#include "lemon/server.h"
#include "lemon/hf_variants.h"
#include "lemon/config_file.h"
#include "lemon/ollama_api.h"
#include "lemon/backends/sd_server.h"
#include "lemon/backends/backend_utils.h"
#include <cstring>
#include "lemon/utils/json_utils.h"
#include "lemon/utils/path_utils.h"
#include "lemon/streaming_proxy.h"
#include "lemon/logging_config.h"
#include "lemon/runtime_config.h"
#include "lemon/system_info.h"
#include "lemon/version.h"
#include <iostream>
#include <iomanip>
#include <sstream>
#include <fstream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>
#include <filesystem>
#include <algorithm>
#include <cmath>
#include <lemon/utils/aixlog.hpp>

#ifdef _WIN32
    #include <windows.h>
    #include <winsock2.h>
    #include <ws2tcpip.h>
#else
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netdb.h>  // Crucial for getaddrinfo and addrinfo struct
    #include <unistd.h>
#endif

#ifdef __APPLE__
    #include <sys/sysctl.h>
#endif

#ifdef __linux__
    #include <sys/ioctl.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <libdrm/drm.h>
    #include "lemon/amdxdna_accel.h"
#endif

namespace fs = std::filesystem;

namespace lemon {

namespace {

bool should_disable_thinking(const json& request_json) {
    // enable_thinking takes precedence over thinking when both are present.
    if (request_json.contains("enable_thinking") && request_json["enable_thinking"].is_boolean()) {
        return request_json["enable_thinking"].get<bool>() == false;
    }

    if (request_json.contains("thinking")) {
        const auto& thinking = request_json["thinking"];
        if (thinking.is_boolean()) {
            return thinking.get<bool>() == false;
        }
        if (thinking.is_object()) {
            const std::string type = thinking.value("type", "");
            if (type == "disabled") {
                return true;
            }
            if (type == "enabled") {
                return false;
            }
        }
    }

    return false;
}

bool strip_handled_thinking_fields(json& request_json) {
    bool modified = false;
    modified = request_json.erase("enable_thinking") > 0 || modified;
    modified = request_json.erase("thinking") > 0 || modified;
    return modified;
}

bool prepend_no_think_to_last_user_message(json& request_json) {
    if (!request_json.contains("messages") || !request_json["messages"].is_array()) {
        LOG(DEBUG, "Server") << "No messages array found for /no_think injection" << std::endl;
        return false;
    }

    auto& messages = request_json["messages"];

    for (int i = static_cast<int>(messages.size()) - 1; i >= 0; i--) {
        if (messages[i].is_object() &&
            messages[i].contains("role") &&
            messages[i]["role"].is_string() &&
            messages[i]["role"].get<std::string>() == "user" &&
            messages[i].contains("content") &&
            messages[i]["content"].is_string()) {

            std::string original_content = messages[i]["content"].get<std::string>();
            messages[i]["content"] = "/no_think\n" + original_content;
            return true;
        }
    }

    LOG(DEBUG, "Server") << "No string-content user message found for /no_think injection" << std::endl;
    return false;
}

} // namespace


static const json MIME_TYPES = {
    {"mp3",  "audio/mpeg"},
    {"opus", "audio/opus"},
    {"aac",  "audio/aac"},
    {"flac", "audio/flac"},
    {"wav",  "audio/wav"},
    {"pcm",  "audio/l16;rate=24000;endianness=little-endian"}
};

Server::Server(std::shared_ptr<RuntimeConfig> config, const std::string& cache_dir)
    : config_(config),
      cache_dir_(cache_dir),
      port_(config->port()), running_(false), udp_beacon_() {

    // Set global HttpClient timeout
    utils::HttpClient::set_default_timeout(config->global_timeout());

    model_manager_ = std::make_unique<ModelManager>();

    // Set extra models directory for GGUF discovery
    model_manager_->set_extra_models_dir(config_->extra_models_dir());

    backend_manager_ = std::make_unique<BackendManager>();
    BackendManager::set_global(backend_manager_.get());

    router_ = std::make_unique<Router>(config_.get(),
                                       model_manager_.get(),
                                       backend_manager_.get());

    LOG(DEBUG, "Server") << "Debug logging enabled - subprocess output will be visible" << std::endl;

    const char* api_key_env = std::getenv("LEMONADE_API_KEY");
    api_key_ = api_key_env ? std::string(api_key_env) : "";

    // Read admin API key - if not set, defaults to regular API key value
    const char* admin_api_key_env = std::getenv("LEMONADE_ADMIN_API_KEY");
    if (admin_api_key_env) {
        admin_api_key_ = std::string(admin_api_key_env);
    } else {
        admin_api_key_ = api_key_;
    }

    setup_http_servers();

    // Initialize WebSocket server for realtime API and log streaming
    websocket_server_ = std::make_unique<WebSocketServer>(
        router_.get(),
        config_->host(),
        config_->websocket_port());
}

void Server::setup_http_servers() {
    http_server_ = std::make_unique<httplib::Server>();
    http_server_v6_ = std::make_unique<httplib::Server>();

    // CRITICAL: Enable multi-threading so the server can handle concurrent requests
    // Without this, the server is single-threaded and blocks on long operations

    std::function<httplib::TaskQueue *(void)> task_queue_factory = [this] {
        LOG(DEBUG, "Server") << "Creating new thread pool with 8 threads" << std::endl;
        return new httplib::ThreadPool(8);
    };

    http_server_->new_task_queue = task_queue_factory;
    http_server_v6_->new_task_queue = task_queue_factory;

    setup_routes(*http_server_);
    setup_routes(*http_server_v6_);
}

Server::~Server() {
    stop();
}

void Server::log_request(const httplib::Request& req) {
    if (req.path != "/api/v0/health" && req.path != "/api/v1/health" &&
        req.path != "/v0/health" && req.path != "/v1/health" &&
        req.path != "/api/v0/system-stats" && req.path != "/api/v1/system-stats" &&
        req.path != "/v0/system-stats" && req.path != "/v1/system-stats" &&
        req.path != "/api/v0/stats" && req.path != "/api/v1/stats" &&
        req.path != "/v0/stats" && req.path != "/v1/stats" &&
        req.path != "/live") {
        LOG(DEBUG, "Server") << req.method << " " << req.path << std::endl;
    }
}

httplib::Server::HandlerResponse Server::authenticate_request(const httplib::Request& req, httplib::Response& res) {
    // Check if path requires authentication (API routes and internal endpoints)
    bool is_api_route = (req.path.rfind("/api/", 0) == 0) ||
                        (req.path.rfind("/v0/", 0) == 0) ||
                        (req.path.rfind("/v1/", 0) == 0);
    bool is_internal_route = (req.path.rfind("/internal/", 0) == 0);

    // Internal endpoints are restricted to loopback regardless of API key
    if (is_internal_route) {
        bool is_loopback = (req.remote_addr == "127.0.0.1" || req.remote_addr == "::1");
        if (!is_loopback) {
            LOG(WARNING, "Server") << "Rejected internal request from non-loopback address: "
                        << req.remote_addr << " " << req.path << std::endl;
            res.status = 403;
            res.set_content("{\"error\": \"Internal endpoints are only accessible from localhost\"}", "application/json");
            return httplib::Server::HandlerResponse::Handled;
        }
    }

    // Authentication hierarchy:
    // - Admin key: access to both internal and regular API endpoints
    // - Regular API key: access only to regular API endpoints (not internal)
    // - If admin key is not set, it defaults to regular API key value
    // - If only admin key is set, regular endpoints are accessible without auth, internal requires admin key
    // - If no keys are set, all endpoints are accessible without auth

    std::string auth_token = httplib::get_bearer_token_auth(req);

    if (is_internal_route) {
        // Internal routes require admin key authentication
        if (!admin_api_key_.empty() && req.method != "OPTIONS") {
            if (auth_token != admin_api_key_) {
                res.status = 401;
                res.set_content("{\"error\": \"Invalid or missing admin API key\"}", "application/json");
                return httplib::Server::HandlerResponse::Handled;
            }
        }
    } else if (is_api_route && req.method != "OPTIONS") {
        if (!api_key_.empty()) {
            if ((auth_token != api_key_) && (auth_token != admin_api_key_)) {
                res.status = 401;
                res.set_content("{\"error\": \"Invalid or missing API key\"}", "application/json");
                return httplib::Server::HandlerResponse::Handled;
            }
        }
    }

    return httplib::Server::HandlerResponse::Unhandled;
}


void Server::setup_routes(httplib::Server &web_server) {
    // Add pre-routing handler to log ALL incoming requests (except health checks)
    web_server.set_pre_routing_handler([this](const httplib::Request& req, httplib::Response& res) {
        this->log_request(req);
        return authenticate_request(req, res);
    });

    web_server.Get("/live", [this](const httplib::Request& req, httplib::Response& res) {
        handle_live(req, res);
    });

    // Setup CORS for all routes
    setup_cors(web_server);

    // Helper lambda to register routes for both v0 and v1 (with and without /api prefix for OpenAI compatibility)
    auto register_get = [this, &web_server](const std::string& endpoint,
                               std::function<void(const httplib::Request&, httplib::Response&)> handler) {
        web_server.Get("/api/v0/" + endpoint, handler);
        web_server.Get("/api/v1/" + endpoint, handler);
        web_server.Get("/v0/" + endpoint, handler);
        web_server.Get("/v1/" + endpoint, handler);
    };

    auto register_post = [this, &web_server](const std::string& endpoint,
                                std::function<void(const httplib::Request&, httplib::Response&)> handler) {
        web_server.Post("/api/v0/" + endpoint, handler);
        web_server.Post("/api/v1/" + endpoint, handler);
        web_server.Post("/v0/" + endpoint, handler);
        web_server.Post("/v1/" + endpoint, handler);
        // Also register as GET for HEAD request support (HEAD uses GET handler)
        // Return 405 Method Not Allowed (endpoint exists but wrong method)
        web_server.Get("/api/v0/" + endpoint, [](const httplib::Request&, httplib::Response& res) {
            res.status = 405;
            res.set_content("{\"error\": \"Method Not Allowed. Use POST for this endpoint\"}", "application/json");
        });
        web_server.Get("/api/v1/" + endpoint, [](const httplib::Request&, httplib::Response& res) {
            res.status = 405;
            res.set_content("{\"error\": \"Method Not Allowed. Use POST for this endpoint\"}", "application/json");
        });
        web_server.Get("/v0/" + endpoint, [](const httplib::Request&, httplib::Response& res) {
            res.status = 405;
            res.set_content("{\"error\": \"Method Not Allowed. Use POST for this endpoint\"}", "application/json");
        });
        web_server.Get("/v1/" + endpoint, [](const httplib::Request&, httplib::Response& res) {
            res.status = 405;
            res.set_content("{\"error\": \"Method Not Allowed. Use POST for this endpoint\"}", "application/json");
        });
    };

    // Health check
    register_get("health", [this](const httplib::Request& req, httplib::Response& res) {
        handle_health(req, res);
    });

    // Models endpoints
    register_get("models", [this](const httplib::Request& req, httplib::Response& res) {
        handle_models(req, res);
    });

    // Model by ID (need to register for both versions with regex, with and without /api prefix)
    web_server.Get(R"(/api/v0/models/(.+))", [this](const httplib::Request& req, httplib::Response& res) {
        handle_model_by_id(req, res);
    });
    web_server.Get(R"(/api/v1/models/(.+))", [this](const httplib::Request& req, httplib::Response& res) {
        handle_model_by_id(req, res);
    });
    web_server.Get(R"(/v0/models/(.+))", [this](const httplib::Request& req, httplib::Response& res) {
        handle_model_by_id(req, res);
    });
    web_server.Get(R"(/v1/models/(.+))", [this](const httplib::Request& req, httplib::Response& res) {
        handle_model_by_id(req, res);
    });

    // Chat completions (OpenAI compatible)
    register_post("chat/completions", [this](const httplib::Request& req, httplib::Response& res) {
        handle_chat_completions(req, res);
    });

    // Completions
    register_post("completions", [this](const httplib::Request& req, httplib::Response& res) {
        handle_completions(req, res);
    });

    // Embeddings
    register_post("embeddings", [this](const httplib::Request& req, httplib::Response& res) {
        handle_embeddings(req, res);
    });

    // Reranking
    register_post("reranking", [this](const httplib::Request& req, httplib::Response& res) {
        handle_reranking(req, res);
    });

    // Audio endpoints (OpenAI /v1/audio/* compatible)
    register_post("audio/transcriptions", [this](const httplib::Request& req, httplib::Response& res) {
        handle_audio_transcriptions(req, res);
    });

    // Speech
    register_post("audio/speech", [this](const httplib::Request& req, httplib::Response& res) {
        handle_audio_speech(req, res);
    });

    // Image endpoints (OpenAI /v1/images/* compatible)
    register_post("images/generations", [this](const httplib::Request& req, httplib::Response& res) {
        handle_image_generations(req, res);
    });
    register_post("images/edits", [this](const httplib::Request& req, httplib::Response& res) {
        handle_image_edits(req, res);
    });
    register_post("images/variations", [this](const httplib::Request& req, httplib::Response& res) {
        handle_image_variations(req, res);
    });
    register_post("images/upscale", [this](const httplib::Request& req, httplib::Response& res) {
        handle_image_upscale(req, res);
    });
    // Responses endpoint
    register_post("responses", [this](const httplib::Request& req, httplib::Response& res) {
        handle_responses(req, res);
    });

    // Model management endpoints
    register_post("pull", [this](const httplib::Request& req, httplib::Response& res) {
        handle_pull(req, res);
    });

    register_get("pull/variants", [this](const httplib::Request& req, httplib::Response& res) {
        handle_pull_variants(req, res);
    });

    register_post("load", [this](const httplib::Request& req, httplib::Response& res) {
        handle_load(req, res);
    });

    register_post("unload", [this](const httplib::Request& req, httplib::Response& res) {
        handle_unload(req, res);
    });

    register_post("delete", [this](const httplib::Request& req, httplib::Response& res) {
        handle_delete(req, res);
    });

    register_post("params", [this](const httplib::Request& req, httplib::Response& res) {
        handle_params(req, res);
    });

    // Backend management endpoints
    register_post("install", [this](const httplib::Request& req, httplib::Response& res) {
        handle_install(req, res);
    });

    register_post("uninstall", [this](const httplib::Request& req, httplib::Response& res) {
        handle_uninstall(req, res);
    });

    // System endpoints
    register_get("stats", [this](const httplib::Request& req, httplib::Response& res) {
        handle_stats(req, res);
    });

    register_get("system-info", [this](const httplib::Request& req, httplib::Response& res) {
        handle_system_info(req, res);
    });

    register_get("system-stats", [this](const httplib::Request& req, httplib::Response& res) {
        handle_system_stats(req, res);
    });

    register_post("log-level", [this](const httplib::Request& req, httplib::Response& res) {
        handle_log_level(req, res);
    });


    // NOTE: /api/v1/halt endpoint removed - use SIGTERM signal instead (like Python server)
    // The stop command now sends termination signal directly to the process

    // Internal shutdown endpoint (not part of public API)
    web_server.Post("/internal/shutdown", [this](const httplib::Request& req, httplib::Response& res) {
        handle_shutdown(req, res);
    });

    // Unified config endpoints (not part of public API)
    web_server.Post("/internal/set", [this](const httplib::Request& req, httplib::Response& res) {
        handle_config_set(req, res);
    });
    web_server.Get("/internal/config", [this](const httplib::Request& req, httplib::Response& res) {
        handle_config_get(req, res);
    });
    web_server.Post("/internal/cleanup-cache", [this](const httplib::Request& req, httplib::Response& res) {
        handle_cleanup_cache(req, res);
    });

    // Test endpoint to verify POST works
    web_server.Post("/api/v1/test", [](const httplib::Request& req, httplib::Response& res) {
        LOG(INFO, "Server") << "TEST POST endpoint hit!" << std::endl;
        res.set_content("{\"test\": \"ok\"}", "application/json");
    });

    // Register Ollama-compatible API routes
    auto ollama_api = std::make_shared<OllamaApi>(router_.get(), model_manager_.get());
    ollama_api->register_routes(web_server);

    // Setup static file serving for web UI
    setup_static_files(web_server);
}

void Server::setup_static_files(httplib::Server &web_server) {
    // Determine static files directory (relative to executable)
    std::string static_dir = utils::get_resource_path("resources/static");

    // Create a reusable handler for serving index.html with template variable replacement
    auto serve_index_html = [this, static_dir](const httplib::Request&, httplib::Response& res) {
        std::string index_path = static_dir + "/index.html";
        std::ifstream file(index_path);

        if (!file.is_open()) {
            LOG(ERROR, "Server") << "Could not open index.html at: " << index_path << std::endl;
            res.status = 404;
            res.set_content("{\"error\": \"index.html not found\"}", "application/json");
            return;
        }

        // Read the entire file
        std::string html_template((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        file.close();

        // Get filtered models from model manager
        auto models_map = model_manager_->get_supported_models();

        // Convert map to JSON
        json filtered_models = json::object();
        for (const auto& [model_name, info] : models_map) {
            filtered_models[model_name] = {
                {"model_name", model_name},
                {"checkpoint", info.checkpoint()},
                {"recipe", info.recipe},
                {"labels", info.labels},
                {"suggested", info.suggested},
                {"composite_models", info.composite_models},
                {"mmproj", info.mmproj()}
            };

            // Add size if available
            if (info.size > 0.0) {
                filtered_models[model_name]["size"] = info.size;
            }
        }

        // Create JavaScript snippets
        std::string server_models_js = "<script>window.SERVER_MODELS = " + filtered_models.dump() + ";</script>";

        // Get platform name
        std::string platform_name;
        #ifdef _WIN32
            platform_name = "Windows";
        #elif __APPLE__
            platform_name = "Darwin";
        #elif __linux__
            platform_name = "Linux";
        #else
            platform_name = "Unknown";
        #endif
        std::string platform_js = "<script>window.PLATFORM = '" + platform_name + "';</script>";

        // Replace template variables
        size_t pos;

        // Replace {{SERVER_PORT}}
        while ((pos = html_template.find("{{SERVER_PORT}}")) != std::string::npos) {
            html_template.replace(pos, 15, std::to_string(port_));
        }

        // Replace {{SERVER_MODELS_JS}}
        while ((pos = html_template.find("{{SERVER_MODELS_JS}}")) != std::string::npos) {
            html_template.replace(pos, 20, server_models_js);
        }

        // Replace {{PLATFORM_JS}}
        while ((pos = html_template.find("{{PLATFORM_JS}}")) != std::string::npos) {
            html_template.replace(pos, 15, platform_js);
        }

        // Set no-cache headers
        res.set_header("Cache-Control", "no-cache, no-store, must-revalidate");
        res.set_header("Pragma", "no-cache");
        res.set_header("Expires", "0");
        res.set_content(html_template, "text/html");
    };

    // Keep status page at /status endpoint
    web_server.Get("/status", serve_index_html);

    // Also serve index.html at /api/v1 for compatibility
    web_server.Get("/api/v1", serve_index_html);

    // Mount static files directory for status page assets (CSS, JS, images)
    if (!web_server.set_mount_point("/static", static_dir)) {
        LOG(WARNING, "Server") << "Could not mount static files from: " << static_dir << std::endl;
        LOG(WARNING, "Server") << "Status page assets will not be available" << std::endl;
    }

    // Web app UI endpoint - serve the React web app at root
    std::string web_app_dir = utils::get_resource_path("resources/web-app");

    // Check if web app directory exists
    if (fs::exists(web_app_dir) && fs::is_directory(web_app_dir)) {
        // Create a handler for serving web app index.html for SPA routing
        auto serve_web_app_html = [web_app_dir](const httplib::Request&, httplib::Response& res) {
            std::string index_path = web_app_dir + "/index.html";
            std::ifstream file(index_path);

            if (!file.is_open()) {
                res.status = 404;
                res.set_content("{\"error\": \"Web app not found\"}", "application/json");
                return;
            }

            std::string html((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
            file.close();

            // Inject mock window.api for web compatibility with the shared Tauri app renderer
            std::string mock_api = R"(
<script>
// Mock window.api for web compatibility (the Tauri shim is skipped in pure-web mode)
window.api = {
    isWebApp: true,  // Explicit flag to indicate web mode
    platform: navigator.platform || 'web',
    minimizeWindow: () => {},
    maximizeWindow: () => {},
    closeWindow: () => {},
    openExternal: (url) => window.open(url, '_blank'),
    onMaximizeChange: () => {},
    updateMinWidth: () => {},
    zoomIn: () => document.body.style.zoom = (parseFloat(document.body.style.zoom || '1') + 0.1).toString(),
    zoomOut: () => document.body.style.zoom = (parseFloat(document.body.style.zoom || '1') - 0.1).toString(),
    getSettings: async () => {
        const saved = localStorage.getItem('lemonade-settings');
        if (saved) return JSON.parse(saved);
        // Return defaults matching DEFAULT_LAYOUT_SETTINGS from appSettings.ts
        return {
            layout: {
                isChatVisible: true,
                isModelManagerVisible: true,
                isCenterPanelVisible: true,
                isLogsVisible: false,
                modelManagerWidth: 280,
                chatWidth: 350,
                logsHeight: 200
            },
            theme: 'dark',
            apiUrl: window.location.origin,
            apiKey: { value: '' }
        };
    },
    saveSettings: async (settings) => {
        localStorage.setItem('lemonade-settings', JSON.stringify(settings));
        return settings;
    },
    onSettingsUpdated: () => {},
    getServerPort: () => parseInt(window.location.port) || 13305,
    onServerPortUpdated: () => {},
    getServerAPIKey: async () => {
        const settings = await window.api.getSettings();
        return settings.apiKey?.value || '';
    },
    restartApp: () => window.location.reload()
};
</script>
)";

            // Insert mock API before the closing </head> tag
            size_t head_end_pos = html.find("</head>");
            if (head_end_pos != std::string::npos) {
                html.insert(head_end_pos, mock_api);
            }

            // Set no-cache headers
            res.set_header("Cache-Control", "no-cache, no-store, must-revalidate");
            res.set_header("Pragma", "no-cache");
            res.set_header("Expires", "0");
            res.set_content(html, "text/html");
        };

        // Serve the web app's index.html at root and for SPA routes
        web_server.Get("/", serve_web_app_html);

        // Also serve at /web-app for backwards compatibility
        web_server.Get("/web-app/?", serve_web_app_html);

        // Serve all static assets from the web app directory (JS, CSS, fonts, assets, etc.)
        // Handle both root-level assets and /web-app/ prefixed paths for backwards compatibility
        auto serve_web_app_asset = [web_app_dir](const httplib::Request& req, httplib::Response& res, const std::string& file_path) {
            std::string full_path = web_app_dir + "/" + file_path;

            // Serve the file
            std::ifstream file(full_path, std::ios::binary);
            if (!file.is_open()) {
                res.status = 404;
                res.set_content("File not found", "text/plain");
                return;
            }

            // Read file content
            std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
            file.close();

            // Determine content type based on extension
            std::string content_type = "application/octet-stream";
            size_t dot_pos = file_path.rfind('.');
            if (dot_pos != std::string::npos) {
                std::string ext = file_path.substr(dot_pos);
                if (ext == ".js") content_type = "text/javascript";
                else if (ext == ".css") content_type = "text/css";
                else if (ext == ".html") content_type = "text/html";
                else if (ext == ".woff") content_type = "font/woff";
                else if (ext == ".woff2") content_type = "font/woff2";
                else if (ext == ".ttf") content_type = "font/ttf";
                else if (ext == ".svg") content_type = "image/svg+xml";
                else if (ext == ".png") content_type = "image/png";
                else if (ext == ".jpg" || ext == ".jpeg") content_type = "image/jpeg";
                else if (ext == ".json") content_type = "application/json";
                else if (ext == ".ico") content_type = "image/x-icon";
            }

            res.set_content(content, content_type);
        };

        // Serve favicon from web-app directory at root
        web_server.Get("/favicon.ico", [serve_web_app_asset](const httplib::Request& req, httplib::Response& res) {
            serve_web_app_asset(req, res, "favicon.ico");
        });

        // Serve web app assets from root (for files like renderer.bundle.js, fonts, etc.)
        web_server.Get(R"(/([^/]+\.(js|css|woff|woff2|ttf|svg|png|jpg|jpeg|json|ico)))",
                      [serve_web_app_asset](const httplib::Request& req, httplib::Response& res) {
            std::string file_path = req.matches[1].str();
            serve_web_app_asset(req, res, file_path);
        });

        // Keep /web-app/ prefix routes for backwards compatibility
        web_server.Get(R"(/web-app/(.+))", [serve_web_app_asset](const httplib::Request& req, httplib::Response& res) {
            std::string file_path = req.matches[1].str();
            serve_web_app_asset(req, res, file_path);
        });

        // SPA fallback: serve index.html for any unmatched GET routes that don't start with /api, /v0, /v1, /static, or /live
        // This enables client-side routing
        web_server.Get(R"(^(?!/api|/v0|/v1|/static|/live|/status|/internal).*)",
                      [serve_web_app_html](const httplib::Request& req, httplib::Response& res) {
            // Only serve index.html if the path doesn't look like a file with extension
            std::string path = req.path;
            size_t last_slash = path.rfind('/');
            std::string last_segment = (last_slash != std::string::npos) ? path.substr(last_slash + 1) : path;

            // If the last segment has an extension and it's not .html, let it 404
            // (This helps catch missing assets more clearly)
            size_t dot_pos = last_segment.rfind('.');
            if (dot_pos != std::string::npos) {
                std::string ext = last_segment.substr(dot_pos);
                if (ext != ".html" && ext != ".htm") {
                    // File with extension not found, return 404
                    res.status = 404;
                    return;
                }
            }

            // Otherwise, serve the SPA index.html for client-side routing
            serve_web_app_html(req, res);
        });
    } else {
        // Fallback to static page when web-app is not compiled
        LOG(INFO, "Server") << "Web app directory not found at: " << web_app_dir << std::endl;
        LOG(INFO, "Server") << "Falling back to static status page at root" << std::endl;

        // Serve the static status page at root instead
        web_server.Get("/", serve_index_html);

        // Serve favicon from static directory
        web_server.Get("/favicon.ico", [static_dir](const httplib::Request& req, httplib::Response& res) {
            std::ifstream ifs(static_dir + "/favicon.ico", std::ios::binary);
            if (ifs) {
                std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
                res.set_content(content, "image/x-icon");
                res.status = 200;
            } else {
                res.set_content("Favicon not found.", "text/plain");
                res.status = 404;
            }
        });
    }

    // Override default headers for static files to include no-cache
    // This ensures the web UI always gets the latest version
    web_server.set_file_request_handler([](const httplib::Request& req, httplib::Response& res) {
        // Add no-cache headers for static files
        res.set_header("Cache-Control", "no-cache, no-store, must-revalidate");
        res.set_header("Pragma", "no-cache");
        res.set_header("Expires", "0");
    });
}

void Server::setup_cors(httplib::Server &web_server) {
    // Set CORS headers for all responses
    web_server.set_default_headers({
        {"Access-Control-Allow-Origin", "*"},
        {"Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"},
        {"Access-Control-Allow-Headers", "Content-Type, Authorization"}
    });

    // Handle preflight OPTIONS requests
    web_server.Options(".*", [](const httplib::Request&, httplib::Response& res) {
        res.status = 204;
    });

    // Catch-all error handler - must be last!
    web_server.set_error_handler([](const httplib::Request& req, httplib::Response& res) {
        LOG(ERROR, "Server") << "Error " << res.status << ": " << req.method << " " << req.path << std::endl;

        if (res.status == 404) {
            // Only set generic "endpoint not found" if no content was already set
            // This preserves specific error messages (e.g., "model not found")
            if (res.body.empty()) {
                nlohmann::json error = {
                    {"error", {
                        {"message", "The requested endpoint does not exist"},
                        {"type", "not_found"},
                        {"path", req.path}
                    }}
                };
                res.set_content(error.dump(), "application/json");
            }
        } else if (res.status == 400) {
            // Log more details about 400 errors
            LOG(ERROR, "Server") << "400 Bad Request details - Body length: " << req.body.length()
                      << ", Content-Type: " << req.get_header_value("Content-Type") << std::endl;
            // Ensure a response is sent
            if (res.body.empty()) {
                nlohmann::json error = {
                    {"error", {
                        {"message", "Bad request"},
                        {"type", "bad_request"}
                    }}
                };
                res.set_content(error.dump(), "application/json");
            }
        }
    });
}

std::string Server::resolve_host_to_ip(int ai_family, const std::string& host) {
    struct addrinfo hints = {0};
    hints.ai_family = ai_family;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = 0; // No AI_ADDRCONFIG: allows loopback resolution when offline

    struct addrinfo *result = nullptr;

    // Check return value (0 is success)
    if (getaddrinfo(host.c_str(), nullptr, &hints, &result) != 0) {
        LOG(WARNING, "Server") << "resolution failed for " << host << " no " << (ai_family == AF_INET ? "IPv4" : ai_family == AF_INET6 ? "IPv6" : "") << " resolution found." << std::endl;
        return ""; // Return empty string on failure, don't return void
    }

    if (result == nullptr) return "";

    // Use INET6_ADDRSTRLEN to be safe for both (it's larger)
    char addrstr[INET6_ADDRSTRLEN];
    void *ptr = nullptr;

    // Safety Check - verify what we actually got back
    if (result->ai_family == AF_INET) {
        struct sockaddr_in *ipv4 = (struct sockaddr_in *)result->ai_addr;
        ptr = &(ipv4->sin_addr);
    } else if (result->ai_family == AF_INET6) {
        struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)result->ai_addr;
        ptr = &(ipv6->sin6_addr);
    } else {
        freeaddrinfo(result);
        return "";
    }

    // Convert binary IP to string
    inet_ntop(result->ai_family, ptr, addrstr, sizeof(addrstr));

    std::string resolved_ip(addrstr);
    freeaddrinfo(result);
    return resolved_ip;
}

void Server::setup_http_logger(httplib::Server &web_server) {
    // Add request logging for ALL requests (except health checks and stats endpoints)
    web_server.set_logger([this](const httplib::Request& req, const httplib::Response& res) {
        // Skip logging health checks and stats endpoints to reduce log noise
        if (req.path == "/api/v0/health" || req.path == "/api/v1/health" ||
            req.path == "/v0/health" || req.path == "/v1/health" || req.path == "/live" ||
            req.path == "/api/v0/system-stats" || req.path == "/api/v1/system-stats" ||
            req.path == "/v0/system-stats" || req.path == "/v1/system-stats" ||
            req.path == "/api/v0/stats" || req.path == "/api/v1/stats" ||
            req.path == "/v0/stats" || req.path == "/v1/stats") {
            return;
        }

        // Determine if this is a high-signal request or noise (static assets, repeated queries)
        bool is_quiet_get = (req.method == "GET" && (
            req.path == "/" ||
            req.path == "/api/v0/models" || req.path == "/api/v1/models" ||
            req.path == "/v0/models" || req.path == "/v1/models" ||
            req.path == "/api/v0/system-info" || req.path == "/api/v1/system-info" ||
            req.path == "/v0/system-info" || req.path == "/v1/system-info" ||
            req.path == "/api/v0/system-checks" || req.path == "/api/v1/system-checks" ||
            req.path == "/v0/system-checks" || req.path == "/v1/system-checks" ||
            req.path.find(".js") != std::string::npos ||
            req.path.find(".css") != std::string::npos ||
            req.path.find(".svg") != std::string::npos ||
            req.path.find(".png") != std::string::npos ||
            req.path.find(".ico") != std::string::npos ||
            req.path.find(".woff") != std::string::npos
        ));

        if (!is_quiet_get) {
            LOG(DEBUG, "Server") << req.method << " " << req.path << " - " << res.status << std::endl;
        }
    });
}

void Server::run() {
    std::string host = config_->host();
    LOG(INFO, "Server") << "Starting HTTP server on " << host << ":" << port_ << std::endl;

    std::string ipv4 = resolve_host_to_ip(AF_INET, host);
    std::string ipv6 = resolve_host_to_ip(AF_INET6, host);

    LOG(INFO, "Server") << "Host resolution: IPv4=" << (ipv4.empty() ? "(none)" : ipv4)
                        << ", IPv6=" << (ipv6.empty() ? "(none)" : ipv6) << std::endl;

    if (ipv4.empty() && ipv6.empty()) {
        throw std::runtime_error("Failed to resolve host '" + host + "' to any address. "
                                 "Cannot start server.");
    }

    running_ = true;

    // Start WebSocket server for realtime API and log streaming
    if (websocket_server_) {
        if (websocket_server_->start()) {
            LOG(INFO, "Server") << "WebSocket server started on port "
                                << websocket_server_->get_port() << std::endl;
        } else {
            LOG(WARNING, "Server") << "Failed to start WebSocket server" << std::endl;
        }
    }

    while (true) {
        std::atomic<bool> listener_started(false);
        std::atomic<bool> listener_start_failed(false);

        if (!ipv4.empty()) {
            // setup ipv4 thread
            setup_http_logger(*http_server_);
            http_v4_thread_ = std::thread([this, ipv4, &listener_started, &listener_start_failed]() {
                LOG(INFO, "Server") << "Binding IPv4 HTTP server to " << ipv4 << ":" << port_ << "..." << std::endl;
                int result = http_server_->bind_to_port(ipv4, port_);
                if (result <= 0) {
                    LOG(ERROR, "Server") << "Failed to bind IPv4 HTTP server to " << ipv4 << ":" << port_ << std::endl;
                    listener_start_failed = true;
                    return;
                }
                LOG(INFO, "Server") << "IPv4 HTTP server listening on " << ipv4 << ":" << port_ << std::endl;
                listener_started = true;
                if (!http_server_->listen_after_bind()) {
                    LOG(ERROR, "Server") << "IPv4 HTTP server listen_after_bind() failed" << std::endl;
                    listener_start_failed = true;
                }
            });
        }
        if (!ipv6.empty()) {
            // setup ipv6 thread
            setup_http_logger(*http_server_v6_);
            http_v6_thread_ = std::thread([this, ipv6, &listener_started, &listener_start_failed]() {
                LOG(INFO, "Server") << "Binding IPv6 HTTP server to [" << ipv6 << "]:" << port_ << "..." << std::endl;
                int result = http_server_v6_->bind_to_port(ipv6, port_);
                if (result <= 0) {
                    LOG(ERROR, "Server") << "Failed to bind IPv6 HTTP server to [" << ipv6 << "]:" << port_ << std::endl;
                    listener_start_failed = true;
                    return;
                }
                LOG(INFO, "Server") << "IPv6 HTTP server listening on [" << ipv6 << "]:" << port_ << std::endl;
                listener_started = true;
                if (!http_server_v6_->listen_after_bind()) {
                    LOG(ERROR, "Server") << "IPv6 HTTP server listen_after_bind() failed" << std::endl;
                    listener_start_failed = true;
                }
            });
        }

        // Enumerate all RFC1918 interfaces to determine if we can broadcast.
        // The beacon will send per-interface with the correct IP in the payload.
        auto rfc1918Interfaces = udp_beacon_.getLocalRFC1918Interfaces();
        bool no_bcast = config_->no_broadcast();
        if (!rfc1918Interfaces.empty() && !no_bcast) {
            std::cout << "[Server] [Net Broadcast] Broadcasting on " << rfc1918Interfaces.size()
                      << " RFC1918 interface(s):";
            for (const auto& iface : rfc1918Interfaces) {
                std::cout << " " << iface.ipAddress << " (bcast " << iface.broadcastAddress << ")";
            }
            std::cout << std::endl;
            udp_beacon_.startBroadcasting(
                13305, // Broadcast port best to not make it adjustable, so clients dont have to scan.
                port_,
                2
            );
        } else if (!rfc1918Interfaces.empty() && no_bcast) {
            LOG(INFO, "Server") << "Broadcasting disabled by --no-broadcast option" << std::endl;
        } else {
            LOG(INFO, "Server") << "Unable to broadcast my existance please use a RFC1918 IPv4," << std::endl
                        << "or hostname that resolves to RFC1918 IPv4." << std::endl;
        }

        if (http_v4_thread_.joinable())
            http_v4_thread_.join();
        if (http_v6_thread_.joinable())
            http_v6_thread_.join();

        if (!listener_started && listener_start_failed) {
            if (rebind_requested_) {
                // Port rebind failed (e.g. port in use) — restore old port and retry
                LOG(ERROR, "Server") << "Failed to bind to new port " << port_
                            << ", will not retry" << std::endl;
                rebind_requested_ = false;
                break;
            }
            std::cerr << "[Server] Another Lemonade router/server instance is already running on "
                      << config_->host() << ":" << port_ << ". Duplicate instance now exiting." << std::endl;
            stop();
            break;
        }

        if (!rebind_requested_) {
            break;  // Normal exit (stop() was called)
        }

        // Rebind requested: re-resolve host, recreate HTTP servers, loop back to bind+listen
        host = config_->host();
        ipv4 = resolve_host_to_ip(AF_INET, host);
        ipv6 = resolve_host_to_ip(AF_INET6, host);
        LOG(INFO, "Server") << "Rebinding to " << host << ":" << port_ << "..." << std::endl;
        rebind_requested_ = false;
        setup_http_servers();
    }
}

void Server::stop() {
    if (running_) {
        LOG(INFO, "Server") << "Stopping HTTP server..." << std::endl;
        udp_beacon_.stopBroadcasting();
        http_server_v6_->stop();
        http_server_->stop();
        running_ = false;

        // Stop WebSocket server
        if (websocket_server_) {
            LOG(INFO, "Server") << "Stopping WebSocket server..." << std::endl;
            websocket_server_->stop();
        }

        // Explicitly clean up router (unload models, stop backend servers)
        if (router_) {
            LOG(INFO, "Server") << "Unloading models and stopping backend servers..." << std::endl;
            try {
                router_->unload_model();
            } catch (const std::exception& e) {
                LOG(ERROR, "Server") << "Error during cleanup: " << e.what() << std::endl;
            }
        }
        LOG(INFO, "Server") << "Cleanup complete" << std::endl;
    }
}

bool Server::is_running() const {
    return running_;
}

// Generates an actionable error message for model loading failures.
// Handles three cases:
//   1. Model exists but was filtered out (e.g., NPU model on non-NPU system)
//   2. Model doesn't exist in the registry at all
//   3. Model exists but failed to load (engine error)
nlohmann::json Server::create_model_error(const std::string& requested_model, const std::string& exception_msg) {
    nlohmann::json error_response;

    // Case 1: Check if this model exists but was filtered out due to system requirements
    std::string filter_reason = model_manager_->get_model_filter_reason(requested_model);

    if (!filter_reason.empty()) {
        // Model exists but is not available on this system
        std::string message = "Model '" + requested_model + "' is not available on this system. " + filter_reason;

        error_response["error"] = {
            {"message", message},
            {"type", "model_not_supported"},
            {"param", "model"},
            {"code", "model_not_supported"},
            {"requested_model", requested_model}
        };

        return error_response;
    }

    // Case 2: Check if model doesn't exist in the registry at all
    if (!model_manager_->model_exists(requested_model)) {
        std::string message = "Model '" + requested_model + "' was not found. ";

        // Get available models and suggest some
        auto available_models = model_manager_->get_supported_models();

        if (!available_models.empty()) {
            // Collect model names
            std::vector<std::string> model_names;
            model_names.reserve(available_models.size());
            for (const auto& [name, info] : available_models) {
                model_names.push_back(name);
            }

            // Sort alphabetically for consistent output
            std::sort(model_names.begin(), model_names.end());

            // Show up to 3 available models
            const size_t max_suggestions = 3;
            size_t count = std::min(model_names.size(), max_suggestions);

            message += "Available models include: ";
            for (size_t i = 0; i < count; ++i) {
                if (i > 0) message += ", ";
                message += "'" + model_names[i] + "'";
            }

            if (model_names.size() > max_suggestions) {
                message += ", and " + std::to_string(model_names.size() - max_suggestions) + " more";
            }
            message += ". ";
        }

        message += "Use 'lemonade-server list' or GET /api/v1/models?show_all=true to see all available models.";

        // Add FLM hint for -FLM model names when FLM is not ready
        if (requested_model.size() > 4 &&
            requested_model.substr(requested_model.size() - 4) == "-FLM") {
            auto flm_status = SystemInfoCache::get_flm_status();
            if (!flm_status.is_ready()) {
                message += " The FLM backend is not ready: " + flm_status.message + ".";
                if (!flm_status.action.empty()) {
                    message += " " + flm_status.action + ".";
                }
            }
        }

        error_response["error"] = {
            {"message", message},
            {"type", "model_not_found"},
            {"param", "model"},
            {"code", "model_not_found"},
            {"requested_model", requested_model}
        };

        return error_response;
    }

    // Case 3: Model exists and is available, but failed to load (engine error)
    // Return the actual exception message so the user knows what went wrong
    std::string message = "Failed to load model '" + requested_model + "': " + exception_msg;

    error_response["error"] = {
        {"message", message},
        {"type", "model_load_error"},
        {"param", "model"},
        {"code", "model_load_error"},
        {"requested_model", requested_model}
    };

    return error_response;
}

// This function is called by:
//   - handle_chat_completions() - /chat/completions endpoint
//   - handle_completions() - /completions endpoint
//   - handle_load() - /load endpoint
//
// Behavior:
//   1. If model is already loaded: Return immediately (no-op)
//   2. If model is not downloaded: Download it (first-time use)
//   3. If model is downloaded: Use cached version (don't check HuggingFace for updates)
//
// Note: Only the /pull endpoint checks HuggingFace for updates (do_not_upgrade=false)
void Server::auto_load_model_if_needed(const std::string& requested_model) {
    // Check if this specific model is already loaded (multi-model aware)
    if (router_->is_model_loaded(requested_model)) {
        LOG(INFO, "Server") << "Model already loaded: " << requested_model << std::endl;
        return;
    }

    // Log the auto-loading action
    LOG(INFO, "Server") << "Auto-loading model: " << requested_model << std::endl;

    // Get model info
    if (!model_manager_->model_exists(requested_model)) {
        throw std::runtime_error("Model not found: " + requested_model);
    }

    auto info = model_manager_->get_model_info(requested_model);

    // Download model if not cached (first-time use)
    // IMPORTANT: Use do_not_upgrade=true to prevent checking HuggingFace for updates
    // This means:
    //   - If model is NOT downloaded: Download it from HuggingFace
    //   - If model IS downloaded: Skip HuggingFace API check entirely (use cached version)
    // Only the /pull endpoint should check for updates (uses do_not_upgrade=false)
    if (info.recipe != "flm" && !model_manager_->is_model_downloaded(requested_model)) {
        LOG(INFO, "Server") << "Model not cached, downloading from Hugging Face..." << std::endl;
        LOG(INFO, "Server") << "This may take several minutes for large models." << std::endl;
        model_manager_->download_registered_model(info, true);
        LOG(INFO, "Server") << "Model download complete: " << requested_model << std::endl;

        // CRITICAL: Refresh model info after download to get correct resolved_path
        // The resolved_path is computed based on filesystem, so we need fresh info now that files exist
        info = model_manager_->get_model_info(requested_model);
    }

    // Load model with do_not_upgrade=true
    // For FLM models: FastFlowLMServer will handle download internally if needed
    // For non-FLM models: Model should already be cached at this point
    router_->load_model(requested_model, info, RecipeOptions(info.recipe, json::object()), true);
    LOG(INFO, "Server") << "Model loaded successfully: " << requested_model << std::endl;
}

void Server::handle_health(const httplib::Request& req, httplib::Response& res) {
    // For HEAD requests, just return 200 OK without processing
    if (req.method == "HEAD") {
        res.status = 200;
        return;
    }

    nlohmann::json response = {{"status", "ok"}};

    // Add version information
    response["version"] = LEMON_VERSION_STRING;

    // Add model loaded information like Python implementation
    std::string loaded_model = router_->get_loaded_model();

    response["model_loaded"] = loaded_model.empty() ? nlohmann::json(nullptr) : nlohmann::json(loaded_model);

    // Multi-model support: Add all loaded models
    response["all_models_loaded"] = router_->get_all_loaded_models();

    // Add max model limits
    response["max_models"] = router_->get_max_model_limits();

    // Add WebSocket server port for realtime API and log streaming
    if (websocket_server_ && websocket_server_->is_running()) {
        response["websocket_port"] = websocket_server_->get_port();
    }

    res.set_content(response.dump(), "application/json");
}

void Server::handle_live(const httplib::Request& req, httplib::Response& res) {
    // For HEAD requests, just return 200 OK
    if (req.method == "HEAD") {
        res.status = 200;
        return;
    }

    // liveness response
    static const char* kLiveResponse = R"({"status":"ok"})";

    res.set_content(kLiveResponse, "application/json");
    res.status = 200;
}

void Server::handle_models(const httplib::Request& req, httplib::Response& res) {
    // For HEAD requests, just return 200 OK without processing
    if (req.method == "HEAD") {
        res.status = 200;
        return;
    }

    // Check if we should show all models (for CLI list command) or only downloaded (OpenAI API behavior)
    bool show_all = req.has_param("show_all") && req.get_param_value("show_all") == "true";

    // OPTIMIZATION: For OpenAI API mode, use get_downloaded_models() which filters first
    // Only use get_supported_models() when we need to show ALL models
    std::map<std::string, ModelInfo> models;
    if (show_all) {
        models = model_manager_->get_supported_models();
    } else {
        models = model_manager_->get_downloaded_models();
    }

    nlohmann::json response;
    response["data"] = nlohmann::json::array();
    response["object"] = "list";

    for (const auto& [model_id, model_info] : models) {
        response["data"].push_back(model_info_to_json(model_id, model_info));
    }

    res.set_content(response.dump(), "application/json");
}

nlohmann::json Server::model_info_to_json(const std::string& model_id, const ModelInfo& info) {
    nlohmann::json model_json = {
        {"id", model_id},
        {"object", "model"},
        {"created", 1234567890},
        {"owned_by", "lemonade"},
        {"checkpoint", info.checkpoint()},
        {"checkpoints", info.checkpoints},
        {"recipe", info.recipe},
        {"downloaded", info.downloaded},
        {"suggested", info.suggested},
        {"labels", info.labels},
        {"composite_models", info.composite_models},
        {"recipe_options", info.recipe_options.to_json()},
    };

    // Add size if available
    if (info.size > 0.0) {
        model_json["size"] = info.size;
    }

    // Add image_defaults if present (for sd-cpp models)
    if (info.image_defaults.has_defaults) {
        json img_def = {
            {"steps", info.image_defaults.steps},
            {"cfg_scale", info.image_defaults.cfg_scale},
            {"width", info.image_defaults.width},
            {"height", info.image_defaults.height}
        };
        if (!info.image_defaults.sampling_method.empty())
            img_def["sampling_method"] = info.image_defaults.sampling_method;
        if (info.image_defaults.flow_shift > 0.0f)
            img_def["flow_shift"] = info.image_defaults.flow_shift;
        model_json["image_defaults"] = img_def;
    }

    return model_json;
}

void Server::handle_model_by_id(const httplib::Request& req, httplib::Response& res) {
    std::string model_id = req.matches[1];

    if (model_manager_->model_exists(model_id)) {
        auto info = model_manager_->get_model_info(model_id);
        res.set_content(model_info_to_json(model_id, info).dump(), "application/json");
    } else {
        res.status = 404;
        auto error_response = create_model_error(model_id, "Model not found");
        res.set_content(error_response.dump(), "application/json");
    }
}

void Server::handle_chat_completions(const httplib::Request& req, httplib::Response& res) {
    try {
        auto request_json = nlohmann::json::parse(req.body);

        // Debug: Check if tools are present
        if (request_json.contains("tools")) {
            LOG(DEBUG, "Server") << "Tools present in request: " << request_json["tools"].size() << " tool(s)" << std::endl;
            LOG(DEBUG, "Server") << "Tools JSON: " << request_json["tools"].dump() << std::endl;
        } else {
            LOG(DEBUG, "Server") << "No tools in request" << std::endl;
        }

        // Handle model loading/switching
        if (request_json.contains("model")) {
            std::string requested_model = request_json["model"];
            try {
                auto_load_model_if_needed(requested_model);
            } catch (const std::exception& e) {
                LOG(ERROR, "Server") << "Failed to load model: " << e.what() << std::endl;
                auto error_response = create_model_error(requested_model, e.what());
                // Set appropriate status code based on error type
                std::string error_code = error_response["error"]["code"].get<std::string>();
                if (error_code == "model_load_error") {
                    res.status = 500;  // Internal server error - model exists but failed to load
                } else {
                    res.status = 404;  // Not found - model doesn't exist or is filtered out
                }
                res.set_content(error_response.dump(), "application/json");
                return;
            }
        } else if (!router_->is_model_loaded()) {
            LOG(ERROR, "Server") << "No model loaded and no model specified in request" << std::endl;
            res.status = 400;
            res.set_content("{\"error\": \"No model loaded and no model specified in request\"}", "application/json");
            return;
        }

        // Check if the loaded model supports chat completion (only LLM models do)
        std::string model_to_check = request_json.contains("model") ? request_json["model"].get<std::string>() : "";
        if (router_->get_model_type(model_to_check) != ModelType::LLM) {
            LOG(ERROR, "Server") << "Model does not support chat completion" << std::endl;
            res.status = 400;
            res.set_content(R"({"error": {"message": "This model does not support chat completion. Only LLM models support this endpoint.", "type": "invalid_request_error"}})", "application/json");
            return;
        }

        // Check if streaming is requested
        bool is_streaming = request_json.contains("stream") && request_json["stream"].get<bool>();

        // Use original request body - each backend (FLM, llamacpp, etc.) handles
        // model name transformation internally via their forward methods
        std::string request_body = req.body;
        bool request_modified = false;

        // OpenCode and other OpenAI-compatible clients may send thinking=false
        // instead of Lemonade's enable_thinking=false.
        if (should_disable_thinking(request_json)) {
            request_modified = prepend_no_think_to_last_user_message(request_json);
        }
        request_modified = strip_handled_thinking_fields(request_json) || request_modified;

        // If we modified the request, serialize it back to string
        if (request_modified) {
            request_body = request_json.dump();
        }

        if (is_streaming) {
            try {
                // Log the HTTP request
                LOG(INFO, "Server") << "POST /api/v1/chat/completions - Streaming" << std::endl;

                // Set up streaming response with SSE headers
                res.set_header("Cache-Control", "no-cache");
                res.set_header("Connection", "keep-alive");
                res.set_header("X-Accel-Buffering", "no"); // Disable nginx buffering

                // Use cpp-httplib's chunked content provider for SSE streaming
                res.set_chunked_content_provider(
                    "text/event-stream",
                    [this, request_body](size_t offset, httplib::DataSink& sink) {
                        // For chunked responses, offset tracks bytes sent so far
                        // We only want to stream once when offset is 0
                        if (offset > 0) {
                            return false; // We're done after the first call
                        }

                        // Use unified Router path for streaming
                        router_->chat_completion_stream(request_body, sink);

                        return false;
                    }
                );
            } catch (const std::exception& e) {
                LOG(ERROR, "Server") << "Streaming failed: " << e.what() << std::endl;
                res.status = 500;
                res.set_content("{\"error\":\"Internal server error during streaming\"}", "application/json");
            }
        } else {
            // Log the HTTP request
            LOG(INFO, "Server") << "POST /api/v1/chat/completions - 200 OK" << std::endl;

            auto response = router_->chat_completion(request_json);

            // Debug: Check if response contains tool_calls
            if (response.contains("choices") && response["choices"].is_array() && !response["choices"].empty()) {
                auto& first_choice = response["choices"][0];
                if (first_choice.contains("message")) {
                    auto& message = first_choice["message"];
                    if (message.contains("tool_calls")) {
                        LOG(DEBUG, "Server") << "Response contains tool_calls: " << message["tool_calls"].dump() << std::endl;
                    } else {
                        LOG(DEBUG, "Server") << "Response message does NOT contain tool_calls" << std::endl;
                        if (message.contains("content")) {
                            LOG(DEBUG, "Server") << "Message content: " << message["content"].get<std::string>().substr(0, 200) << std::endl;
                        }
                    }
                }
            }

            res.set_content(response.dump(), "application/json");

            // Print and save telemetry for non-streaming
            // llama-server includes timing data in the response under "timings" field
            if (response.contains("timings")) {
                auto timings = response["timings"];
                int input_tokens = 0;
                int output_tokens = 0;
                double ttft_seconds = 0.0;
                double tps = 0.0;

                LOG(INFO, "Telemetry") << "=== Telemetry ===" << std::endl;
                if (timings.contains("prompt_n")) {
                    input_tokens = timings["prompt_n"].get<int>();
                    LOG(INFO, "Telemetry") << "Input tokens:  " << input_tokens << std::endl;
                }
                if (timings.contains("predicted_n")) {
                    output_tokens = timings["predicted_n"].get<int>();
                    LOG(INFO, "Telemetry") << "Output tokens: " << output_tokens << std::endl;
                }
                if (timings.contains("prompt_ms")) {
                    ttft_seconds = timings["prompt_ms"].get<double>() / 1000.0;
                    LOG(INFO, "Telemetry") << "TTFT (s):      " << std::fixed << std::setprecision(2)
                             << ttft_seconds << std::endl;
                }
                if (timings.contains("predicted_per_second")) {
                    tps = timings["predicted_per_second"].get<double>();
                    LOG(INFO, "Telemetry") << "TPS:           " << std::fixed << std::setprecision(2)
                             << tps << std::endl;
                }
                LOG(INFO, "Telemetry") << "=================" << std::endl;

                // Save telemetry to router
                router_->update_telemetry(input_tokens, output_tokens, ttft_seconds, tps);
            } else if (response.contains("usage")) {
                // OpenAI format uses "usage" field
                auto usage = response["usage"];
                int input_tokens = 0;
                int output_tokens = 0;
                double ttft_seconds = 0.0;
                double tps = 0.0;

                LOG(INFO, "Telemetry") << "=== Telemetry ===" << std::endl;
                if (usage.contains("prompt_tokens")) {
                    input_tokens = usage["prompt_tokens"].get<int>();
                    LOG(INFO, "Telemetry") << "Input tokens:  " << input_tokens << std::endl;
                }
                if (usage.contains("completion_tokens")) {
                    output_tokens = usage["completion_tokens"].get<int>();
                    LOG(INFO, "Telemetry") << "Output tokens: " << output_tokens << std::endl;
                }

                // FLM format may include timing data
                if (usage.contains("prefill_duration_ttft")) {
                    ttft_seconds = usage["prefill_duration_ttft"].get<double>();
                    LOG(INFO, "Telemetry") << "TTFT (s):      " << std::fixed << std::setprecision(2)
                             << ttft_seconds << std::endl;
                }
                if (usage.contains("decoding_speed_tps")) {
                    tps = usage["decoding_speed_tps"].get<double>();
                    LOG(INFO, "Telemetry") << "TPS:           " << std::fixed << std::setprecision(2)
                             << tps << std::endl;
                }
                LOG(INFO, "Telemetry") << "=================" << std::endl;

                // Save telemetry to router
                router_->update_telemetry(input_tokens, output_tokens, ttft_seconds, tps);
            }

            // Capture prompt_tokens from usage if available
            if (response.contains("usage")) {
                auto usage = response["usage"];
                if (usage.contains("prompt_tokens")) {
                    int prompt_tokens = usage["prompt_tokens"].get<int>();
                    router_->update_prompt_tokens(prompt_tokens);
                }
            }
        }

    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "Chat completion failed: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_completions(const httplib::Request& req, httplib::Response& res) {
    try {
        auto request_json = nlohmann::json::parse(req.body);

        // Handle model loading/switching (same logic as chat_completions)
        if (request_json.contains("model")) {
            std::string requested_model = request_json["model"];
            try {
                auto_load_model_if_needed(requested_model);
            } catch (const std::exception& e) {
                LOG(ERROR, "Server") << "Failed to load model: " << e.what() << std::endl;
                auto error_response = create_model_error(requested_model, e.what());
                // Set appropriate status code based on error type
                std::string error_code = error_response["error"]["code"].get<std::string>();
                if (error_code == "model_load_error") {
                    res.status = 500;  // Internal server error - model exists but failed to load
                } else {
                    res.status = 404;  // Not found - model doesn't exist or is filtered out
                }
                res.set_content(error_response.dump(), "application/json");
                return;
            }
        } else if (!router_->is_model_loaded()) {
            LOG(ERROR, "Server") << "No model loaded and no model specified in request" << std::endl;
            res.status = 400;
            res.set_content("{\"error\": \"No model loaded and no model specified in request\"}", "application/json");
            return;
        }

        // Check if the loaded model supports completion (only LLM models do)
        std::string model_to_check = request_json.contains("model") ? request_json["model"].get<std::string>() : "";
        if (router_->get_model_type(model_to_check) != ModelType::LLM) {
            LOG(ERROR, "Server") << "Model does not support completion" << std::endl;
            res.status = 400;
            res.set_content(R"({"error": {"message": "This model does not support completion. Only LLM models support this endpoint.", "type": "invalid_request_error"}})", "application/json");
            return;
        }

        // Check if streaming is requested
        bool is_streaming = request_json.contains("stream") && request_json["stream"].get<bool>();

        // Use original request body - each backend handles model name transformation internally
        std::string request_body = req.body;

        if (is_streaming) {
            try {
                // Log the HTTP request
                LOG(INFO, "Server") << "POST /api/v1/completions - Streaming" << std::endl;

                // Set up SSE headers
                res.set_header("Cache-Control", "no-cache");
                res.set_header("Connection", "keep-alive");
                res.set_header("X-Accel-Buffering", "no");

                res.set_chunked_content_provider(
                    "text/event-stream",
                    [this, request_body](size_t offset, httplib::DataSink& sink) {
                        if (offset > 0) {
                            return false; // Already sent everything
                        }

                        // Use unified Router path for streaming
                        router_->completion_stream(request_body, sink);

                        return false;
                    }
                );

                LOG(INFO, "Server") << "Streaming completed - 200 OK" << std::endl;
                return;

            } catch (const std::exception& e) {
                LOG(ERROR, "Server") << "Streaming failed: " << e.what() << std::endl;
                res.status = 500;
                res.set_content("{\"error\": \"" + std::string(e.what()) + "\"}", "application/json");
                return;
            }
        } else {
            // Non-streaming
            auto response = router_->completion(request_json);

            // Check if response contains an error
            if (response.contains("error")) {
                LOG(ERROR, "Server") << "Backend returned error response: " << response["error"].dump() << std::endl;
                res.status = 500;
                res.set_content(response.dump(), "application/json");
                return;
            }

            // Verify response has required fields
            if (!response.contains("choices")) {
                LOG(ERROR, "Server") << "Response missing 'choices' field. Response: " << response.dump() << std::endl;
                res.status = 500;
                nlohmann::json error = {{"error", "Backend returned invalid response format"}};
                res.set_content(error.dump(), "application/json");
                return;
            }

            res.set_content(response.dump(), "application/json");

            // Print and save telemetry for non-streaming completions
            if (response.contains("timings")) {
                auto timings = response["timings"];
                int input_tokens = 0;
                int output_tokens = 0;
                double ttft_seconds = 0.0;
                double tps = 0.0;

                LOG(INFO, "Telemetry") << "=== Telemetry ===" << std::endl;
                if (timings.contains("prompt_n")) {
                    input_tokens = timings["prompt_n"].get<int>();
                    LOG(INFO, "Telemetry") << "Input tokens:  " << input_tokens << std::endl;
                }
                if (timings.contains("predicted_n")) {
                    output_tokens = timings["predicted_n"].get<int>();
                    LOG(INFO, "Telemetry") << "Output tokens: " << output_tokens << std::endl;
                }
                if (timings.contains("prompt_ms")) {
                    ttft_seconds = timings["prompt_ms"].get<double>() / 1000.0;
                    LOG(INFO, "Telemetry") << "TTFT (s):      " << std::fixed << std::setprecision(2)
                             << ttft_seconds << std::endl;
                }
                if (timings.contains("predicted_per_second")) {
                    tps = timings["predicted_per_second"].get<double>();
                    LOG(INFO, "Telemetry") << "TPS:           " << std::fixed << std::setprecision(2)
                             << tps << std::endl;
                }
                LOG(INFO, "Telemetry") << "=================" << std::endl;

                // Save telemetry to router
                router_->update_telemetry(input_tokens, output_tokens, ttft_seconds, tps);
            } else if (response.contains("usage")) {
                auto usage = response["usage"];
                int input_tokens = 0;
                int output_tokens = 0;
                double ttft_seconds = 0.0;
                double tps = 0.0;

                LOG(INFO, "Telemetry") << "=== Telemetry ===" << std::endl;
                if (usage.contains("prompt_tokens")) {
                    input_tokens = usage["prompt_tokens"].get<int>();
                    LOG(INFO, "Telemetry") << "Input tokens:  " << input_tokens << std::endl;
                }
                if (usage.contains("completion_tokens")) {
                    output_tokens = usage["completion_tokens"].get<int>();
                    LOG(INFO, "Telemetry") << "Output tokens: " << output_tokens << std::endl;
                }

                // FLM format may include timing data
                if (usage.contains("prefill_duration_ttft")) {
                    ttft_seconds = usage["prefill_duration_ttft"].get<double>();
                    LOG(INFO, "Telemetry") << "TTFT (s):      " << std::fixed << std::setprecision(2)
                             << ttft_seconds << std::endl;
                }
                if (usage.contains("decoding_speed_tps")) {
                    tps = usage["decoding_speed_tps"].get<double>();
                    LOG(INFO, "Telemetry") << "TPS:           " << std::fixed << std::setprecision(2)
                             << tps << std::endl;
                }
                LOG(INFO, "Telemetry") << "=================" << std::endl;

                // Save telemetry to router
                router_->update_telemetry(input_tokens, output_tokens, ttft_seconds, tps);
            }

            // Capture prompt_tokens from usage if available
            if (response.contains("usage")) {
                auto usage = response["usage"];
                if (usage.contains("prompt_tokens")) {
                    int prompt_tokens = usage["prompt_tokens"].get<int>();
                    router_->update_prompt_tokens(prompt_tokens);
                }
            }
        }

    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_completions: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_embeddings(const httplib::Request& req, httplib::Response& res) {
    try {
        auto request_json = nlohmann::json::parse(req.body);

        // Handle model loading/switching using helper function
        if (request_json.contains("model")) {
            std::string requested_model = request_json["model"];
            try {
                auto_load_model_if_needed(requested_model);
            } catch (const std::exception& e) {
                LOG(ERROR, "Server") << "Failed to load model: " << e.what() << std::endl;
                auto error_response = create_model_error(requested_model, e.what());
                std::string error_code = error_response["error"]["code"].get<std::string>();
                res.status = (error_code == "model_load_error") ? 500 : 404;
                res.set_content(error_response.dump(), "application/json");
                return;
            }
        } else if (!router_->is_model_loaded()) {
            LOG(ERROR, "Server") << "No model loaded and no model specified in request" << std::endl;
            res.status = 400;
            res.set_content("{\"error\": \"No model loaded and no model specified in request\"}", "application/json");
            return;
        }

        // Call router's embeddings method
        auto response = router_->embeddings(request_json);
        res.set_content(response.dump(), "application/json");

    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_embeddings: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_reranking(const httplib::Request& req, httplib::Response& res) {
    try {
        auto request_json = nlohmann::json::parse(req.body);

        // Handle model loading/switching using helper function
        if (request_json.contains("model")) {
            std::string requested_model = request_json["model"];
            try {
                auto_load_model_if_needed(requested_model);
            } catch (const std::exception& e) {
                LOG(ERROR, "Server") << "Failed to load model: " << e.what() << std::endl;
                auto error_response = create_model_error(requested_model, e.what());
                std::string error_code = error_response["error"]["code"].get<std::string>();
                res.status = (error_code == "model_load_error") ? 500 : 404;
                res.set_content(error_response.dump(), "application/json");
                return;
            }
        } else if (!router_->is_model_loaded()) {
            LOG(ERROR, "Server") << "No model loaded and no model specified in request" << std::endl;
            res.status = 400;
            res.set_content("{\"error\": \"No model loaded and no model specified in request\"}", "application/json");
            return;
        }

        // Call router's reranking method
        auto response = router_->reranking(request_json);
        res.set_content(response.dump(), "application/json");

    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_reranking: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_audio_transcriptions(const httplib::Request& req, httplib::Response& res) {
    try {
        LOG(INFO, "Server") << "POST /api/v1/audio/transcriptions" << std::endl;

        // OpenAI audio API uses multipart form data
        if (!req.is_multipart_form_data()) {
            res.status = 400;
            nlohmann::json error = {{"error", {
                {"message", "Request must be multipart/form-data"},
                {"type", "invalid_request_error"}
            }}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        // Build request JSON for router
        nlohmann::json request_json;

        // Extract form fields
        if (req.form.has_field("model")) {
            request_json["model"] = req.form.get_field("model");
        }
        if (req.form.has_field("language")) {
            request_json["language"] = req.form.get_field("language");
        }
        if (req.form.has_field("prompt")) {
            request_json["prompt"] = req.form.get_field("prompt");
        }
        if (req.form.has_field("response_format")) {
            request_json["response_format"] = req.form.get_field("response_format");
        }
        if (req.form.has_field("temperature")) {
            request_json["temperature"] = std::stod(req.form.get_field("temperature"));
        }

        // Extract audio file
        const auto& files = req.form.files;
        bool found_audio = false;
        for (const auto& file_pair : files) {
            if (file_pair.first == "file") {
                const auto& file = file_pair.second;
                request_json["file_data"] = file.content;
                request_json["filename"] = file.filename;
                found_audio = true;
                LOG(INFO, "Server") << "Audio file: " << file.filename
                          << " (" << file.content.size() << " bytes)" << std::endl;
                break;
            }
        }

        if (!found_audio) {
            res.status = 400;
            nlohmann::json error = {{"error", {
                {"message", "Missing 'file' field in request"},
                {"type", "invalid_request_error"}
            }}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        // Handle model loading
        if (request_json.contains("model")) {
            std::string requested_model = request_json["model"];
            try {
                auto_load_model_if_needed(requested_model);
            } catch (const std::exception& e) {
                LOG(ERROR, "Server") << "Failed to load audio model: " << e.what() << std::endl;
                auto error_response = create_model_error(requested_model, e.what());
                std::string error_code = error_response["error"]["code"].get<std::string>();
                res.status = (error_code == "model_load_error") ? 500 : 404;
                res.set_content(error_response.dump(), "application/json");
                return;
            }
        } else {
            res.status = 400;
            nlohmann::json error = {{"error", {
                {"message", "Missing 'model' field in request"},
                {"type", "invalid_request_error"}
            }}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        // Forward to router
        auto response = router_->audio_transcriptions(request_json);

        // Check for error in response
        if (response.contains("error")) {
            res.status = 500;
        }

        res.set_content(response.dump(), "application/json");

    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_audio_transcriptions: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", {
            {"message", e.what()},
            {"type", "internal_error"}
        }}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_audio_speech(const httplib::Request& req, httplib::Response& res) {
    try {
        auto request_json = nlohmann::json::parse(req.body);

        // Handle model loading
        if (request_json.contains("model")) {
            std::string requested_model = request_json["model"];
            try {
                auto_load_model_if_needed(requested_model);
            } catch (const std::exception& e) {
                LOG(ERROR, "Server") << "Failed to load text-to-speech model: " << e.what() << std::endl;
                auto error_response = create_model_error(requested_model, e.what());
                std::string error_code = error_response["error"]["code"].get<std::string>();
                res.status = (error_code == "model_load_error") ? 500 : 404;
                res.set_content(error_response.dump(), "application/json");
                return;
            }
        } else {
            res.status = 400;
            nlohmann::json error = {{"error", {
                {"message", "Missing 'model' field in request"},
                {"type", "invalid_request_error"}
            }}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        if (!request_json.contains("input")) {
            res.status = 400;
            nlohmann::json error = {{"error", {
                {"message", "Missing 'input' field in request"},
                {"type", "invalid_request_error"}
            }}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        bool is_streaming = (request_json.contains("stream") && request_json["stream"].get<bool>());

        if (request_json.contains("stream_format")) {
            is_streaming = true;
            if (request_json["stream_format"] != "audio") {
                res.status = 400;
                nlohmann::json error = {{"error", {
                    {"message", "Only pcm audio streaming format is supported"},
                    {"type", "invalid_request_error"}
                }}};
                res.set_content(error.dump(), "application/json");
                return;
            }
        }

        std::string mime_type;
        if (is_streaming) {
            mime_type = MIME_TYPES["pcm"];
        } else if (request_json.contains("response_format")) {
            if (MIME_TYPES.contains(request_json["response_format"])) {
                mime_type = MIME_TYPES[request_json["response_format"]];
            } else {
                nlohmann::json error = {{"error", {
                    {"message", "Unsupported audio format requested"},
                    {"type", "invalid_request_error"}
                }}};
                res.set_content(error.dump(), "application/json");
                return;
            }
        } else {
            mime_type = MIME_TYPES["mp3"];
        }

        // Log the HTTP request
        LOG(INFO, "Server") << "POST /api/v1/audio/speech" << std::endl;

        res.set_header("Content-Type", mime_type);

        auto audio_source = [this, request_json](size_t offset, httplib::DataSink& sink) {
            // For chunked responses, offset tracks bytes sent so far
            // We only want to stream once when offset is 0
            if (offset > 0) {
                return false; // We're done after the first call
            }

            // Use unified Router path for streaming
            router_->audio_speech(request_json, sink);

            return false;
        };

        if (is_streaming) {
            res.set_header("Cache-Control", "no-cache");
            res.set_header("Connection", "keep-alive");
            res.set_header("X-Accel-Buffering", "no"); // Disable nginx buffering

            // Use cpp-httplib's chunked content provider for streaming
            res.set_chunked_content_provider(mime_type, audio_source);
        } else {
            res.set_content_provider(mime_type, audio_source);
        }

        return;
    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_audio_speech: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", {
            {"message", e.what()},
            {"type", "internal_error"}
        }}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_image_generations(const httplib::Request& req, httplib::Response& res) {
    try {
        LOG(INFO, "Server") << "POST /api/v1/images/generations" << std::endl;

        auto request_json = nlohmann::json::parse(req.body);

        // Validate required fields
        if (!request_json.contains("prompt")) {
            res.status = 400;
            nlohmann::json error = {{"error", {
                {"message", "Missing 'prompt' field in request"},
                {"type", "invalid_request_error"}
            }}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        if (!request_json.contains("model")) {
            res.status = 400;
            nlohmann::json error = {{"error", {
                {"message", "Missing 'model' field in request"},
                {"type", "invalid_request_error"}
            }}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        std::string requested_model = request_json["model"];

        try {
            auto_load_model_if_needed(requested_model);
        } catch (const std::exception& e) {
            LOG(ERROR, "Server") << "Failed to load image model: " << e.what() << std::endl;
            auto error_response = create_model_error(requested_model, e.what());
            std::string error_code = error_response["error"]["code"].get<std::string>();
            res.status = (error_code == "model_load_error") ? 500 : 404;
            res.set_content(error_response.dump(), "application/json");
            return;
        }

        {
            auto response = router_->image_generations(request_json);
            if (response.contains("error")) {
                LOG(ERROR, "Server") << "Image generation backend error: " << response.dump() << std::endl;
                res.status = 500;
            }
            res.set_content(response.dump(), "application/json");
        }

    } catch (const nlohmann::json::exception& e) {
        LOG(ERROR, "Server") << "JSON parse error in handle_image_generations: " << e.what() << std::endl;
        res.status = 400;
        nlohmann::json error = {{"error", {
            {"message", "Invalid JSON: " + std::string(e.what())},
            {"type", "invalid_request_error"}
        }}};
        res.set_content(error.dump(), "application/json");
    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_image_generations: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", {
            {"message", e.what()},
            {"type", "internal_error"}
        }}};
        res.set_content(error.dump(), "application/json");
    }
}

bool Server::parse_n_from_form(const httplib::Request& req, httplib::Response& res, nlohmann::json& out) {
    if (!req.form.has_field("n")) {
        return true;
    }
    int n;
    try {
        size_t pos;
        const std::string& val = req.form.get_field("n");
        n = std::stoi(val, &pos);
        if (pos != val.size()) throw std::invalid_argument("trailing characters");
    } catch (const std::exception&) {
        res.status = 400;
        nlohmann::json error = {{"error", {
            {"message", "Invalid value for 'n': must be an integer"},
            {"type", "invalid_request_error"}
        }}};
        res.set_content(error.dump(), "application/json");
        return false;
    }
    if (n < 1 || n > 10) {
        res.status = 400;
        nlohmann::json error = {{"error", {
            {"message", "Invalid value for 'n': must be between 1 and 10"},
            {"type", "invalid_request_error"}
        }}};
        res.set_content(error.dump(), "application/json");
        return false;
    }
    out["n"] = n;
    return true;
}

bool Server::extract_image_from_form(const httplib::Request& req, httplib::Response& res, nlohmann::json& out) {
    for (const auto& file_pair : req.form.files) {
        if (file_pair.first == "image") {
            const auto& file = file_pair.second;
            out["image_data"] = utils::JsonUtils::base64_encode(file.content);
            out["image_filename"] = file.filename;
            LOG(INFO, "Server") << "Image file: " << file.filename
                      << " (" << file.content.size() << " bytes)" << std::endl;
            return true;
        }
    }
    res.status = 400;
    nlohmann::json error = {{"error", {
        {"message", "Missing 'image' field in request"},
        {"type", "invalid_request_error"}
    }}};
    res.set_content(error.dump(), "application/json");
    return false;
}

bool Server::load_image_model(const nlohmann::json& request_json, httplib::Response& res) {
    if (!request_json.contains("model")) {
        res.status = 400;
        nlohmann::json error = {{"error", {
            {"message", "Missing 'model' field in request"},
            {"type", "invalid_request_error"}
        }}};
        res.set_content(error.dump(), "application/json");
        return false;
    }
    std::string requested_model = request_json["model"];
    try {
        auto_load_model_if_needed(requested_model);
    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "Failed to load image model: " << e.what() << std::endl;
        auto error_response = create_model_error(requested_model, e.what());
        std::string error_code = error_response["error"]["code"].get<std::string>();
        res.status = (error_code == "model_load_error") ? 500 : 404;
        res.set_content(error_response.dump(), "application/json");
        return false;
    }
    return true;
}

void Server::handle_image_edits(const httplib::Request& req, httplib::Response& res) {
    try {
        LOG(INFO, "Server") << "POST /api/v1/images/edits" << std::endl;

        if (!req.is_multipart_form_data()) {
            res.status = 400;
            nlohmann::json error = {{"error", {
                {"message", "Request must be multipart/form-data"},
                {"type", "invalid_request_error"}
            }}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        nlohmann::json request_json;

        // Extract common form fields
        if (req.form.has_field("model"))            request_json["model"]            = req.form.get_field("model");
        if (req.form.has_field("prompt"))           request_json["prompt"]           = req.form.get_field("prompt");
        if (req.form.has_field("size"))             request_json["size"]             = req.form.get_field("size");
        if (req.form.has_field("response_format"))  request_json["response_format"]  = req.form.get_field("response_format");
        if (req.form.has_field("user"))             request_json["user"]             = req.form.get_field("user");
        if (req.form.has_field("background"))       request_json["background"]       = req.form.get_field("background");
        if (req.form.has_field("quality"))          request_json["quality"]          = req.form.get_field("quality");
        if (req.form.has_field("input_fidelity"))   request_json["input_fidelity"]   = req.form.get_field("input_fidelity");

        if (req.form.has_field("output_compression")) {
            int output_compression;
            try {
                size_t pos;
                const std::string& val = req.form.get_field("output_compression");
                output_compression = std::stoi(val, &pos);
                if (pos != val.size()) throw std::invalid_argument("trailing characters");
            } catch (const std::exception&) {
                res.status = 400;
                nlohmann::json error = {{"error", {
                    {"message", "Invalid value for 'output_compression': must be an integer"},
                    {"type", "invalid_request_error"}
                }}};
                res.set_content(error.dump(), "application/json");
                return;
            }
            request_json["output_compression"] = output_compression;
        }

        // Extract optional numeric inference parameters
        auto parse_int_field = [&](const std::string& field) -> bool {
            if (!req.form.has_field(field)) return true;
            const std::string& val = req.form.get_field(field);
            try {
                size_t pos;
                int parsed = std::stoi(val, &pos);
                if (pos != val.size()) throw std::invalid_argument("trailing characters");
                request_json[field] = parsed;
            } catch (const std::exception&) {
                res.status = 400;
                nlohmann::json error = {{"error", {
                    {"message", "Invalid value for '" + field + "': must be an integer"},
                    {"type", "invalid_request_error"}
                }}};
                res.set_content(error.dump(), "application/json");
                return false;
            }
            return true;
        };
        auto parse_float_field = [&](const std::string& field) -> bool {
            if (!req.form.has_field(field)) return true;
            const std::string& val = req.form.get_field(field);
            try {
                size_t pos;
                float parsed = std::stof(val, &pos);
                if (pos != val.size()) throw std::invalid_argument("trailing characters");
                if (std::isnan(parsed) || std::isinf(parsed)) throw std::invalid_argument("nan/inf not allowed");
                request_json[field] = parsed;
            } catch (const std::exception&) {
                res.status = 400;
                nlohmann::json error = {{"error", {
                    {"message", "Invalid value for '" + field + "': must be a number"},
                    {"type", "invalid_request_error"}
                }}};
                res.set_content(error.dump(), "application/json");
                return false;
            }
            return true;
        };
        if (!parse_int_field("steps"))     return;
        if (!parse_float_field("cfg_scale")) return;
        if (!parse_int_field("seed"))      return;

        if (!parse_n_from_form(req, res, request_json))      return;
        if (!extract_image_from_form(req, res, request_json)) return;

        // Extract optional mask file
        for (const auto& file_pair : req.form.files) {
            if (file_pair.first == "mask") {
                const auto& file = file_pair.second;
                request_json["mask_data"] = utils::JsonUtils::base64_encode(file.content);
                request_json["mask_filename"] = file.filename;
                LOG(INFO, "Server") << "Mask file: " << file.filename
                          << " (" << file.content.size() << " bytes)" << std::endl;
                break;
            }
        }

        if (!request_json.contains("prompt")) {
            res.status = 400;
            nlohmann::json error = {{"error", {
                {"message", "Missing 'prompt' field in request"},
                {"type", "invalid_request_error"}
            }}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        if (!load_image_model(request_json, res)) return;

        auto response = router_->image_edits(request_json);
        if (response.contains("error")) {
            LOG(ERROR, "Server") << "Image edits backend error: " << response.dump() << std::endl;
            res.status = 500;
        }
        res.set_content(response.dump(), "application/json");

    } catch (const nlohmann::json::exception& e) {
        LOG(ERROR, "Server") << "JSON parse error in handle_image_edits: " << e.what() << std::endl;
        res.status = 400;
        nlohmann::json error = {{"error", {
            {"message", "Invalid JSON: " + std::string(e.what())},
            {"type", "invalid_request_error"}
        }}};
        res.set_content(error.dump(), "application/json");
    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_image_edits: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", {
            {"message", e.what()},
            {"type", "server_error"}
        }}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_image_variations(const httplib::Request& req, httplib::Response& res) {
    try {
        LOG(INFO, "Server") << "POST /api/v1/images/variations" << std::endl;

        if (!req.is_multipart_form_data()) {
            res.status = 400;
            nlohmann::json error = {{"error", {
                {"message", "Request must be multipart/form-data"},
                {"type", "invalid_request_error"}
            }}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        nlohmann::json request_json;

        // Extract common form fields
        if (req.form.has_field("model"))            request_json["model"]            = req.form.get_field("model");
        if (req.form.has_field("size"))             request_json["size"]             = req.form.get_field("size");
        if (req.form.has_field("response_format"))  request_json["response_format"]  = req.form.get_field("response_format");
        if (req.form.has_field("user"))             request_json["user"]             = req.form.get_field("user");

        if (!parse_n_from_form(req, res, request_json))      return;
        if (!extract_image_from_form(req, res, request_json)) return;
        if (!load_image_model(request_json, res))             return;

        auto response = router_->image_variations(request_json);
        if (response.contains("error")) {
            LOG(ERROR, "Server") << "Image variations backend error: " << response.dump() << std::endl;
            res.status = 500;
        }
        res.set_content(response.dump(), "application/json");

    } catch (const nlohmann::json::exception& e) {
        LOG(ERROR, "Server") << "JSON parse error in handle_image_variations: " << e.what() << std::endl;
        res.status = 400;
        nlohmann::json error = {{"error", {
            {"message", "Invalid JSON: " + std::string(e.what())},
            {"type", "invalid_request_error"}
        }}};
        res.set_content(error.dump(), "application/json");
    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_image_variations: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", {
            {"message", e.what()},
            {"type", "server_error"}
        }}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_image_upscale(const httplib::Request& req, httplib::Response& res) {
    try {
        LOG(INFO, "Server") << "POST /api/v1/images/upscale" << std::endl;

        auto request_json = nlohmann::json::parse(req.body);

        if (!request_json.contains("image") || !request_json["image"].is_string()) {
            res.status = 400;
            nlohmann::json error = {{"error", {
                {"message", "Missing 'image' field (base64 encoded)"},
                {"type", "invalid_request_error"}
            }}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        std::string upscale_model_name = request_json.value("model", "");
        if (upscale_model_name.empty()) {
            res.status = 400;
            nlohmann::json error = {{"error", {
                {"message", "Missing 'model' field"},
                {"type", "invalid_request_error"}
            }}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        std::string upscale_model_path;
        std::string backend;
        try {
            auto info = model_manager_->get_model_info(upscale_model_name);

            if (!model_manager_->is_model_downloaded(upscale_model_name)) {
                LOG(INFO, "Server") << "Upscale model not cached, downloading from Hugging Face..." << std::endl;
                model_manager_->download_registered_model(info, true);
                LOG(INFO, "Server") << "Upscale model download complete: " << upscale_model_name << std::endl;
                info = model_manager_->get_model_info(upscale_model_name);
            }

            upscale_model_path = info.resolved_path("main");

            // Honor explicit config first (e.g. sdcpp.backend = "rocm").
            // "auto" in config.json is mapped to "" by recipe_options().
            auto recipe_opts = config_->recipe_options();
            if (recipe_opts.contains("sd-cpp_backend") &&
                recipe_opts["sd-cpp_backend"].is_string()) {
                backend = recipe_opts["sd-cpp_backend"].get<std::string>();
            }

            // Auto-detect best backend when not explicitly configured,
            // matching the same logic SDServer::load() uses via
            // RecipeOptions::get_option(). Without this, upscaling
            // silently falls back to CPU even when ROCm/Vulkan is available.
            if (backend.empty()) {
                auto supported = SystemInfo::get_supported_backends("sd-cpp");
                if (!supported.backends.empty()) {
                    backend = supported.backends[0];
                } else {
                    backend = "cpu";
                }
            }
        } catch (const std::exception& e) {
            res.status = 404;
            nlohmann::json error = {{"error", {
                {"message", "Upscale model not found: " + upscale_model_name},
                {"type", "invalid_request_error"}
            }}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        // sd-server's HTTP API does not expose an upscaling endpoint.
        // Upscaling is only available via the sd-cli binary's -M upscale mode,
        // so we shell out to sd-cli as a subprocess. This also keeps upscaling
        // as a separate request from generation, which lets the frontend show
        // the original and upscaled images side by side with independent timing.
        std::string exe_dir = lemon::backends::BackendUtils::get_backend_binary_path(
            lemon::backends::SDServer::SPEC, backend);
        std::filesystem::path cli_exe = std::filesystem::path(exe_dir).parent_path() /
#ifdef _WIN32
            "sd-cli.exe";
#else
            "sd-cli";
#endif

        if (!std::filesystem::exists(cli_exe)) {
            res.status = 500;
            nlohmann::json error = {{"error", {
                {"message", "sd-cpp backend not installed (sd-cli not found at: "
                            + cli_exe.string() + ")"},
                {"type", "server_error"}
            }}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        std::vector<std::pair<std::string, std::string>> env_vars;
        std::filesystem::path cli_dir = cli_exe.parent_path();

        std::string resolved_backend = backend;
        if (backend == "rocm") {
            std::string channel = "preview";
            if (config_) {
                channel = config_->rocm_channel();
            }
            resolved_backend = "rocm-" + channel;
        }
#ifndef _WIN32
        std::string lib_path = cli_dir.string();

        if (resolved_backend == "rocm-stable") {
            std::string runtime_dir = lemon::backends::BackendUtils::get_install_directory(
                "rocm-stable-runtime", ""
            );
            if (std::filesystem::exists(runtime_dir)) {
                lib_path = runtime_dir + ":" + lib_path;
            }
        } else if (resolved_backend == "rocm-preview") {
            std::string rocm_arch = SystemInfo::get_rocm_arch();
            if (!rocm_arch.empty()) {
                std::string therock_lib = lemon::backends::BackendUtils::get_therock_lib_path(rocm_arch);
                if (!therock_lib.empty()) {
                    lib_path = therock_lib + ":" + lib_path;
                }
            }
        }

        const char* existing_ld_path = std::getenv("LD_LIBRARY_PATH");
        if (existing_ld_path && strlen(existing_ld_path) > 0) {
            lib_path = lib_path + ":" + std::string(existing_ld_path);
        }
        env_vars.push_back({"LD_LIBRARY_PATH", lib_path});
#else
        if (resolved_backend == "rocm-stable" || resolved_backend == "rocm-preview") {
            std::string new_path = cli_dir.string();

            if (resolved_backend == "rocm-preview") {
                std::string rocm_arch = SystemInfo::get_rocm_arch();
                if (!rocm_arch.empty()) {
                    std::string therock_bin = lemon::backends::BackendUtils::get_therock_lib_path(rocm_arch);
                    if (!therock_bin.empty()) {
                        new_path = therock_bin + ";" + new_path;
                    }
                }
            }

            const char* existing_path = std::getenv("PATH");
            if (existing_path && strlen(existing_path) > 0) new_path += ";" + std::string(existing_path);
            env_vars.push_back({"PATH", new_path});
        }
#endif

        std::string b64_image = request_json["image"].get<std::string>();
        std::string upscaled = lemon::backends::SDServer::upscale_via_cli(
            b64_image, upscale_model_path, cli_exe.string(), env_vars);

        if (upscaled.empty()) {
            res.status = 500;
            nlohmann::json error = {{"error", {
                {"message", "ESRGAN upscale failed"},
                {"type", "server_error"}
            }}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        nlohmann::json response;
        response["created"] = static_cast<long long>(std::time(nullptr));
        response["data"] = nlohmann::json::array();
        response["data"].push_back({{"b64_json", upscaled}});
        res.set_content(response.dump(), "application/json");

    } catch (const nlohmann::json::exception& e) {
        LOG(ERROR, "Server") << "JSON parse error in handle_image_upscale: " << e.what() << std::endl;
        res.status = 400;
        nlohmann::json error = {{"error", {
            {"message", "Invalid JSON: " + std::string(e.what())},
            {"type", "invalid_request_error"}
        }}};
        res.set_content(error.dump(), "application/json");
    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_image_upscale: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", {
            {"message", e.what()},
            {"type", "server_error"}
        }}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_responses(const httplib::Request& req, httplib::Response& res) {
    try {
        auto request_json = nlohmann::json::parse(req.body);

        // Handle model loading/switching using helper function
        if (request_json.contains("model")) {
            std::string requested_model = request_json["model"];
            try {
                auto_load_model_if_needed(requested_model);
            } catch (const std::exception& e) {
                LOG(ERROR, "Server") << "Failed to load model: " << e.what() << std::endl;
                auto error_response = create_model_error(requested_model, e.what());
                std::string error_code = error_response["error"]["code"].get<std::string>();
                res.status = (error_code == "model_load_error") ? 500 : 404;
                res.set_content(error_response.dump(), "application/json");
                return;
            }
        } else if (!router_->is_model_loaded()) {
            LOG(ERROR, "Server") << "No model loaded and no model specified in request" << std::endl;
            res.status = 400;
            res.set_content("{\"error\": \"No model loaded and no model specified in request\"}", "application/json");
            return;
        }

        // Check if streaming is requested
        bool is_streaming = request_json.contains("stream") && request_json["stream"].get<bool>();

        if (is_streaming) {
            try {
                LOG(INFO, "Server") << "POST /api/v1/responses - Streaming" << std::endl;

                // Set up streaming response with SSE headers
                res.set_header("Cache-Control", "no-cache");
                res.set_header("Connection", "keep-alive");
                res.set_header("X-Accel-Buffering", "no");

                // Use cpp-httplib's chunked content provider for SSE streaming
                res.set_chunked_content_provider(
                    "text/event-stream",
                    [this, request_body = req.body](size_t offset, httplib::DataSink& sink) {
                        if (offset > 0) {
                            return false; // Only stream once
                        }

                        // Use unified Router path for streaming
                        router_->responses_stream(request_body, sink);

                        return false;
                    }
                );
            } catch (const std::exception& e) {
                LOG(ERROR, "Server") << "Streaming failed: " << e.what() << std::endl;
                res.status = 500;
                res.set_content("{\"error\":\"Internal server error during streaming\"}", "application/json");
            }
        } else {
            LOG(INFO, "Server") << "POST /api/v1/responses - Non-streaming" << std::endl;

            auto response = router_->responses(request_json);

            LOG(INFO, "Server") << "200 OK" << std::endl;
            res.set_content(response.dump(), "application/json");
        }

    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_responses: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_pull(const httplib::Request& req, httplib::Response& res) {
    try {
        auto request_json = nlohmann::json::parse(req.body);
        // Accept both "model" and "model_name" for compatibility
        std::string model_name = request_json.contains("model") ?
            request_json["model"].get<std::string>() :
            request_json["model_name"].get<std::string>();

        // Extract optional parameters
        std::string checkpoint = request_json.value("checkpoint", "");
        std::string recipe = request_json.value("recipe", "");
        bool do_not_upgrade = request_json.value("do_not_upgrade", false);
        bool stream = request_json.value("stream", false);

        LOG(INFO, "Server") << "Pulling model: " << model_name << std::endl;
        if (!checkpoint.empty()) {
            LOG(INFO, "Server") << "   checkpoint: " << checkpoint << std::endl;
        }
        if (!recipe.empty()) {
            LOG(INFO, "Server") << "   recipe: " << recipe << std::endl;
        }

        // Validate: if checkpoint or recipe are provided, model name must have "user." prefix
        if (!checkpoint.empty() || !recipe.empty()) {
            if (model_name.substr(0, 5) != "user.") {
                res.status = 400;
                nlohmann::json error = {{"error",
                    "When providing 'checkpoint' or 'recipe', the model name must include the "
                    "`user.` prefix, for example `user.Phi-4-Mini-GGUF`. Received: " + model_name}};
                res.set_content(error.dump(), "application/json");
                return;
            }
        }

        // Local import mode: CLI has already copied files to HF cache, just resolve and register
        bool local_import = request_json.value("local_import", false);
        if (local_import) {
            std::string hf_cache = model_manager_->get_hf_cache_dir();
            std::string model_name_clean = model_name.substr(5); // Remove "user." prefix
            std::replace(model_name_clean.begin(), model_name_clean.end(), '/', '-');
            std::string dest_path = hf_cache + "/models--" + model_name_clean;

            LOG(INFO, "Server") << "Local import mode - resolving files in: " << dest_path << std::endl;

            resolve_and_register_local_model(
                dest_path, model_name, request_json, hf_cache
            );

            nlohmann::json response = {
                {"status", "success"},
                {"model_name", model_name},
                {"message", "Model imported and registered successfully"}
            };
            res.set_content(response.dump(), "application/json");
            return;
        }

        if (stream) {
            // SSE streaming mode - send progress events via shared helper
            stream_download_operation(res, [this, model_name, request_json, do_not_upgrade](DownloadProgressCallback progress_cb) {
                model_manager_->download_model(model_name, request_json, do_not_upgrade, progress_cb);
            });
        } else {
            // Legacy synchronous mode - blocks until complete
            model_manager_->download_model(model_name, request_json, do_not_upgrade);

            nlohmann::json response = {{"status", "success"}, {"model_name", model_name}};
            res.set_content(response.dump(), "application/json");
        }

    } catch (const lemon::UnknownModelError& e) {
        LOG(ERROR, "Server") << "ERROR in handle_pull: " << e.what() << std::endl;
        res.status = 400;
        nlohmann::json error = {{"error", e.what()}, {"code", lemon::kUnknownModelErrorCode}};
        res.set_content(error.dump(), "application/json");
    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_pull: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_pull_variants(const httplib::Request& req, httplib::Response& res) {
    try {
        std::string checkpoint = req.get_param_value("checkpoint");
        if (checkpoint.empty()) {
            res.status = 400;
            nlohmann::json error = {{"error", "Missing required query parameter 'checkpoint'"}};
            res.set_content(error.dump(), "application/json");
            return;
        }
        if (checkpoint.find('/') == std::string::npos) {
            res.status = 400;
            nlohmann::json error = {{"error",
                "Malformed 'checkpoint': expected a Hugging Face repo id of the form 'owner/name'"}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        bool not_found = false;
        nlohmann::json body = lemon::fetch_pull_variants(checkpoint, not_found);
        if (not_found) {
            res.status = 404;
            nlohmann::json error = {{"error", "Checkpoint '" + checkpoint + "' not found on Hugging Face"}};
            res.set_content(error.dump(), "application/json");
            return;
        }
        res.set_content(body.dump(), "application/json");
    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_pull_variants: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_load(const httplib::Request& req, httplib::Response& res) {
    auto thread_id = std::this_thread::get_id();
    LOG(DEBUG, "Server") << "===== LOAD ENDPOINT ENTERED (Thread: " << thread_id << ") =====" << std::endl;

    // Declare model_name outside try block so it's available in catch block
    std::string model_name;

    try {
        auto request_json = nlohmann::json::parse(req.body);
        model_name = request_json["model_name"];

        // Get model info
        if (!model_manager_->model_exists(model_name)) {
            LOG(ERROR, "Server") << "Model not found: " << model_name << std::endl;
            res.status = 404;
            auto error_response = create_model_error(model_name, "Model not found");
            res.set_content(error_response.dump(), "application/json");
            return;
        }

        auto info = model_manager_->get_model_info(model_name);

        // Extract optional per-model settings (defaults to -1 / empty = use Router defaults)
        RecipeOptions options = RecipeOptions(info.recipe, request_json);
        bool save_options = request_json.value("save_options", false);

        LOG(INFO, "Server") << "Ensuring model loaded: " << model_name;
        LOG(INFO, "Server") << " " << options.to_log_string(false);
        LOG(INFO, "Server") << std::endl;

        // Persist request options to model info if requested
        if (save_options) {
            info.recipe_options = options;
            model_manager_->save_model_options(info);
        }

        // Download model if needed (first-time use)
        if (!info.downloaded) {
            LOG(INFO, "Server") << "Model not downloaded, downloading..." << std::endl;
            model_manager_->download_registered_model(info);
            info = model_manager_->get_model_info(model_name);
        }

        // Experience models: load each component model instead
        if (info.recipe == "collection" && !info.composite_models.empty()) {
            LOG(INFO, "Server") << "Loading collection components for: " << model_name << std::endl;
            for (const auto& component : info.composite_models) {
                if (!model_manager_->model_exists(component)) {
                    LOG(WARNING, "Server") << "Skipping unknown component: " << component << std::endl;
                    continue;
                }
                if (router_->is_model_loaded(component)) {
                    LOG(INFO, "Server") << "Component already loaded: " << component << std::endl;
                    continue;
                }
                auto comp_info = model_manager_->get_model_info(component);
                if (!comp_info.downloaded) {
                    LOG(INFO, "Server") << "Downloading component: " << component << std::endl;
                    model_manager_->download_registered_model(comp_info);
                    comp_info = model_manager_->get_model_info(component);
                }
                LOG(INFO, "Server") << "Loading component: " << component << std::endl;
                RecipeOptions comp_options = RecipeOptions(comp_info.recipe, request_json);
                router_->load_model(component, comp_info, comp_options, true,
                                    /*allow_reload_on_option_change=*/true);
            }

            nlohmann::json response = {
                {"status", "success"},
                {"model_name", model_name},
                {"recipe", "collection"}
            };
            res.set_content(response.dump(), "application/json");
        } else {
            // Load model with optional per-model settings (declarative: no-op
            // if already loaded with matching options, reload only if options
            // differ)
            router_->load_model(model_name, info, options, true,
                                /*allow_reload_on_option_change=*/true);

            // Return success response
            nlohmann::json response = {
                {"status", "success"},
                {"model_name", model_name},
                {"checkpoint", info.checkpoint()},
                {"recipe", info.recipe}
            };
            res.set_content(response.dump(), "application/json");
        }

    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "Failed to load model: " << e.what() << std::endl;

        // Use consistent error format
        if (!model_name.empty()) {
            auto error_response = create_model_error(model_name, e.what());
            std::string error_code = error_response["error"]["code"].get<std::string>();
            res.status = (error_code == "model_load_error") ? 500 : 404;
            res.set_content(error_response.dump(), "application/json");
        } else {
            // JSON parsing failed before we got model_name - return generic error
            res.status = 400;
            nlohmann::json error = {{"error", {
                {"message", std::string("Invalid request: ") + e.what()},
                {"type", "invalid_request_error"},
                {"code", "invalid_request"}
            }}};
            res.set_content(error.dump(), "application/json");
        }
    }
}

void Server::handle_unload(const httplib::Request& req, httplib::Response& res) {
    try {
        LOG(INFO, "Server") << "Unload request received" << std::endl;
        LOG(DEBUG, "Server") << "Request method: " << req.method << ", body length: " << req.body.length() << std::endl;
        LOG(DEBUG, "Server") << "Content-Type: " << req.get_header_value("Content-Type") << std::endl;

        // Multi-model support: Optional model_name parameter
        std::string model_name;
        if (!req.body.empty()) {
            try {
                auto request_json = nlohmann::json::parse(req.body);
                if (request_json.contains("model_name") && request_json["model_name"].is_string()) {
                    model_name = request_json["model_name"].get<std::string>();
                } else if (request_json.contains("model") && request_json["model"].is_string()) {
                    model_name = request_json["model"].get<std::string>();
                }
            } catch (...) {
                // Ignore parse errors, just unload all
            }
        }

        router_->unload_model(model_name);  // Empty string = unload all

        if (model_name.empty()) {
            LOG(INFO, "Server") << "All models unloaded successfully" << std::endl;
            nlohmann::json response = {
                {"status", "success"},
                {"message", "All models unloaded successfully"}
            };
            res.status = 200;
            res.set_content(response.dump(), "application/json");
        } else {
            LOG(INFO, "Server") << "Model '" << model_name << "' unloaded successfully" << std::endl;
            nlohmann::json response = {
                {"status", "success"},
                {"message", "Model unloaded successfully"},
                {"model_name", model_name}
            };
            res.status = 200;
            res.set_content(response.dump(), "application/json");
        }
    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "Unload failed: " << e.what() << std::endl;

        // Check if error is "Model not loaded" for 404
        std::string error_msg = e.what();
        if (error_msg.find("not loaded") != std::string::npos) {
            res.status = 404;
        } else {
            res.status = 500;
        }

        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_delete(const httplib::Request& req, httplib::Response& res) {
    try {
        auto request_json = nlohmann::json::parse(req.body);
        // Accept both "model" and "model_name" for compatibility
        std::string model_name = request_json.contains("model") ?
            request_json["model"].get<std::string>() :
            request_json["model_name"].get<std::string>();

        LOG(INFO, "Server") << "Deleting model: " << model_name << std::endl;

        // If the model is currently loaded, unload it first to release file locks
        if (router_->is_model_loaded(model_name)) {
            LOG(INFO, "Server") << "Model is loaded, unloading before delete: " << model_name << std::endl;
            router_->unload_model(model_name);
        }

        // Retry delete with delays to handle in-progress downloads releasing file handles
        // This handles the race condition where a cancelled download hasn't yet released
        // its file handles when the delete request arrives
        const int max_retries = 3;
        const int retry_delay_seconds = 5;
        std::string last_error;

        for (int attempt = 0; attempt <= max_retries; ++attempt) {
            try {
                model_manager_->delete_model(model_name);

                // Success - send response and return
                nlohmann::json response = {
                    {"status", "success"},
                    {"message", "Deleted model: " + model_name}
                };
                res.set_content(response.dump(), "application/json");
                return;

            } catch (const std::exception& e) {
                last_error = e.what();

                // Only retry on "file in use" type errors (Windows and POSIX patterns)
                bool is_file_locked =
                    last_error.find("being used by another process") != std::string::npos ||
                    last_error.find("Permission denied") != std::string::npos ||
                    last_error.find("resource busy") != std::string::npos;

                if (is_file_locked && attempt < max_retries) {
                    LOG(INFO, "Server") << "Delete failed (file in use), retry "
                              << (attempt + 1) << "/" << max_retries
                              << " in " << retry_delay_seconds << "s..." << std::endl;
                    std::this_thread::sleep_for(std::chrono::seconds(retry_delay_seconds));
                    continue;
                }

                // Non-retryable error or max retries exceeded - rethrow
                throw;
            }
        }

    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_delete: " << e.what() << std::endl;

        // Check if this is a "Model not found" error (return 422)
        std::string error_msg = e.what();
        if (error_msg.find("Model not found") != std::string::npos ||
            error_msg.find("not supported") != std::string::npos) {
            res.status = 422;
        } else {
            res.status = 500;
        }

        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_cleanup_cache(const httplib::Request& req, httplib::Response& res) {
    try {
        auto request_json = nlohmann::json::parse(req.body);
        bool dry_run = request_json.value("dry_run", true);

        auto result = model_manager_->cleanup_orphaned_cache(dry_run);
        res.set_content(result.dump(), "application/json");
    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_cleanup_cache: " << e.what() << std::endl;
        res.status = 500;
        auto error_response = create_model_error("", e.what());
        res.set_content(error_response.dump(), "application/json");
    }
}

void Server::handle_params(const httplib::Request& req, httplib::Response& res) {
    try {
        auto body = nlohmann::json::parse(req.body);

        // Delegate to RuntimeConfig — accepts all known recipe option keys
        auto result = config_->set(body, [this](const json& applied) {
            apply_config_side_effects(applied);
        });
        res.set_content(result.dump(), "application/json");
    } catch (const nlohmann::json::parse_error& e) {
        LOG(ERROR, "Server") << "ERROR in handle_params: invalid JSON: " << e.what() << std::endl;
        res.status = 400;
        nlohmann::json error = {{"error", "Invalid JSON in request body"}};
        res.set_content(error.dump(), "application/json");
    } catch (const std::invalid_argument& e) {
        res.status = 400;
        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_params: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    }
}

// Called by handle_pull when local_import=true
// Parameters:
//   - dest_path: Directory where model files are located (already copied/uploaded)
//   - model_name: Model name with "user." prefix
//   - model_data: Request content
//   - hf_cache: HuggingFace cache directory for computing relative paths
void Server::resolve_and_register_local_model(
    const std::string& dest_path,
    const std::string& model_name,
    const json& model_data,
    const std::string& hf_cache) {
    std::string mmproj = model_data.value("mmproj", "");
    std::string recipe = model_data.value("recipe", "");
    bool vision = model_data.value("vision", false);

    std::string resolved_checkpoint;
    std::string resolved_mmproj;

    // For RyzenAI LLM models, find genai_config.json
    if (recipe == "ryzenai-llm") {
        for (const auto& entry : std::filesystem::recursive_directory_iterator(dest_path)) {
            if (entry.is_regular_file() && entry.path().filename() == "genai_config.json") {
                resolved_checkpoint = entry.path().parent_path().string();
                break;
            }
        }
        if (resolved_checkpoint.empty()) {
            resolved_checkpoint = dest_path;
        }
    }
    // For llamacpp models, find the GGUF file
    else if (recipe == "llamacpp") {
        std::string gguf_file_found;

        // If no variant or variant not found, search for any .gguf file (excluding mmproj)
        if (gguf_file_found.empty()) {
            for (const auto& entry : std::filesystem::recursive_directory_iterator(dest_path)) {
                if (entry.is_regular_file()) {
                    std::string filename = entry.path().filename().string();
                    std::string filename_lower = filename;
                    std::transform(filename_lower.begin(), filename_lower.end(), filename_lower.begin(), ::tolower);

                    if (filename_lower.find(".gguf") != std::string::npos &&
                        filename_lower.find("mmproj") == std::string::npos) {
                        gguf_file_found = entry.path().string();
                        break;
                    }
                }
            }
        }

        resolved_checkpoint = gguf_file_found.empty() ? dest_path : gguf_file_found;
    }
    // For whispercpp, find .bin file
    else if (recipe == "whispercpp") {
        for (const auto& entry : std::filesystem::recursive_directory_iterator(dest_path)) {
            if (entry.is_regular_file()) {
                std::string filename = entry.path().filename().string();
                if (filename.find(".bin") != std::string::npos) {
                    resolved_checkpoint = entry.path().string();
                    break;
                }
            }
        }
        if (resolved_checkpoint.empty()) {
            resolved_checkpoint = dest_path;
        }
    }

    // Search for mmproj file if vision is enabled or mmproj hint provided
    if (vision || !mmproj.empty()) {
        for (const auto& entry : std::filesystem::recursive_directory_iterator(dest_path)) {
            if (entry.is_regular_file()) {
                std::string filename = entry.path().filename().string();
                std::string filename_lower = filename;
                std::transform(filename_lower.begin(), filename_lower.end(), filename_lower.begin(), ::tolower);

                // Match either the provided mmproj name or any mmproj file
                if (!mmproj.empty() && filename == mmproj) {
                    resolved_mmproj = filename;
                    vision = true;  // Ensure vision is set
                    break;
                } else if (filename_lower.find("mmproj") != std::string::npos) {
                    resolved_mmproj = filename;
                    vision = true;  // Ensure vision is set
                    break;
                }
            }
        }
    }

    // Build checkpoint for registration - store as relative path from HF cache
    std::string checkpoint_to_register;
    std::filesystem::path hf_cache_path = utils::path_from_utf8(hf_cache);
    if (!resolved_checkpoint.empty()) {
        std::filesystem::path rel = std::filesystem::relative(
            utils::path_from_utf8(resolved_checkpoint), hf_cache_path);
        checkpoint_to_register = utils::path_to_utf8(rel);
    } else {
        // Fallback - use dest_path relative to hf_cache
        std::filesystem::path rel = std::filesystem::relative(
            utils::path_from_utf8(dest_path), hf_cache_path);
        checkpoint_to_register = utils::path_to_utf8(rel);
    }

    LOG(INFO, "Server") << "Registering model with checkpoint: " << checkpoint_to_register << std::endl;

    auto actual_model_data = model_data;
    actual_model_data["checkpoint"] = checkpoint_to_register;
    if (!resolved_mmproj.empty()) {
        actual_model_data["mmproj"] = resolved_mmproj;
    }

    // Register the model with source to mark how it was added
    model_manager_->register_user_model(
        model_name,
        actual_model_data,
        "local_upload"
    );

    LOG(INFO, "Server") << "Model registered successfully" << std::endl;
}

void Server::handle_stats(const httplib::Request& req, httplib::Response& res) {
    // For HEAD requests, just return 200 OK without processing
    if (req.method == "HEAD") {
        res.status = 200;
        return;
    }

    try {
        auto stats = router_->get_stats();
        res.set_content(stats.dump(), "application/json");
    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_stats: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_system_info(const httplib::Request& req, httplib::Response& res) {
    // For HEAD requests, just return 200 OK without processing
    if (req.method == "HEAD") {
        res.status = 200;
        return;
    }

    // SystemInfoCache is the single source of truth for hardware + recipes.
    // Recipes are cached until invalidated by install/uninstall.
    nlohmann::json system_info = SystemInfoCache::get_system_info_with_cache();

    // Enrich with release_url, download_filename, version from BackendManager config
    if (system_info.contains("recipes")) {
        enrich_recipes(system_info["recipes"]);
    }

    // Surface runtime config flags that affect client-side install/download UX.
    if (auto* cfg = RuntimeConfig::global()) {
        system_info["no_fetch_executables"] = cfg->no_fetch_executables();
    }

    res.set_content(system_info.dump(), "application/json");
}

// Get CPU usage percentage
double Server::get_cpu_usage() {
#ifdef __linux__
    // Linux: Parse /proc/stat for system-wide CPU usage
    std::lock_guard<std::mutex> lock(cpu_stats_mutex_);

    std::ifstream stat_file("/proc/stat");
    if (!stat_file.is_open()) {
        return -1.0;
    }

    std::string line;
    std::getline(stat_file, line);
    stat_file.close();

    // Parse: "cpu  user nice system idle iowait irq softirq steal"
    std::istringstream iss(line);
    std::string cpu_label;
    uint64_t user, nice, system, idle, iowait, irq, softirq, steal;

    iss >> cpu_label >> user >> nice >> system >> idle >> iowait >> irq >> softirq >> steal;

    uint64_t total_idle = idle + iowait;
    uint64_t total_active = user + nice + system + irq + softirq + steal;
    uint64_t total = total_idle + total_active;

    if (last_cpu_stats_.total > 0) {
        uint64_t idle_diff = total_idle - last_cpu_stats_.total_idle;
        uint64_t total_diff = total - last_cpu_stats_.total;

        last_cpu_stats_.total_idle = total_idle;
        last_cpu_stats_.total = total;

        if (total_diff > 0) {
            return ((total_diff - idle_diff) * 100.0) / total_diff;
        }
    }

    last_cpu_stats_.total_idle = total_idle;
    last_cpu_stats_.total = total;
    return 0.0; // First call, no delta yet

#elif defined(_WIN32)
    // Windows: Use GetSystemTimes for system-wide CPU usage
    std::lock_guard<std::mutex> lock(cpu_stats_mutex_);

    FILETIME idle_time, kernel_time, user_time;
    if (!GetSystemTimes(&idle_time, &kernel_time, &user_time)) {
        return -1.0;
    }

    // Convert FILETIME to uint64_t (100-nanosecond intervals)
    auto filetime_to_uint64 = [](const FILETIME& ft) -> uint64_t {
        return (static_cast<uint64_t>(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
    };

    uint64_t idle = filetime_to_uint64(idle_time);
    uint64_t kernel = filetime_to_uint64(kernel_time); // Includes idle time
    uint64_t user = filetime_to_uint64(user_time);

    // Kernel time includes idle time, so subtract it to get actual kernel time
    uint64_t total = kernel + user;
    uint64_t total_idle = idle;

    if (last_cpu_stats_.total > 0) {
        uint64_t idle_diff = total_idle - last_cpu_stats_.total_idle;
        uint64_t total_diff = total - last_cpu_stats_.total;

        last_cpu_stats_.total_idle = total_idle;
        last_cpu_stats_.total = total;

        if (total_diff > 0) {
            return ((total_diff - idle_diff) * 100.0) / total_diff;
        }
    }

    last_cpu_stats_.total_idle = total_idle;
    last_cpu_stats_.total = total;
    return 0.0; // First call, no delta yet

#elif defined(__APPLE__)
    // macOS: Could use host_processor_info or top command
    return -1.0; // Not implemented yet

#else
    return -1.0;
#endif
}

// Get GPU usage percentage (AMD GPUs on Linux)
double Server::get_gpu_usage() {
#ifdef __linux__
    // Linux: Read from AMD sysfs (gpu_busy_percent)
    // Check all GPUs and return the highest utilization
    try {
        std::string drm_path = "/sys/class/drm";

        if (!fs::exists(drm_path)) {
            return -1.0;
        }

        double highest_usage = -1.0;

        for (const auto& entry : fs::directory_iterator(drm_path)) {
            std::string card_name = entry.path().filename().string();
            if (card_name.find("card") != 0 || card_name.find("-") != std::string::npos) {
                continue;
            }

            std::string busy_path = entry.path().string() + "/device/gpu_busy_percent";
            std::ifstream busy_file(busy_path);
            if (busy_file.is_open()) {
                double usage;
                busy_file >> usage;
                busy_file.close();
                if (usage > highest_usage) {
                    highest_usage = usage;
                }
            }
        }

        return highest_usage;
    } catch (...) {
        return -1.0;
    }

#else
    // GPU usage monitoring not implemented for Windows/macOS
    return -1.0;
#endif
}

// Get VRAM/GTT usage in GB (AMD GPUs on Linux)
double Server::get_vram_usage() {
#ifdef __linux__
    // Linux: Read from AMD sysfs
    // For dGPU: return VRAM used
    // For APU: return VRAM + GTT used
    // On multi-GPU systems, return memory from GPU with highest utilization
    try {
        std::string drm_path = "/sys/class/drm";

        if (!fs::exists(drm_path)) {
            return -1.0;
        }

        double highest_usage = -1.0;
        std::string highest_card;
        double highest_card_memory = 0.0;

        for (const auto& entry : fs::directory_iterator(drm_path)) {
            std::string card_name = entry.path().filename().string();
            if (card_name.find("card") != 0 || card_name.find("-") != std::string::npos) {
                continue;
            }

            std::string device_path = entry.path().string() + "/device";

            // Read GPU utilization to find the most active GPU
            double gpu_usage = 0.0;
            std::ifstream busy_file(device_path + "/gpu_busy_percent");
            if (busy_file.is_open()) {
                busy_file >> gpu_usage;
                busy_file.close();
            }

            // Check if this is a dGPU (has board_info) or APU (no board_info)
            bool is_dgpu = fs::exists(device_path + "/board_info");

            // Read VRAM used
            uint64_t vram_used = 0;
            std::ifstream vram_file(device_path + "/mem_info_vram_used");
            if (vram_file.is_open()) {
                vram_file >> vram_used;
                vram_file.close();
            }

            // Read GTT used
            uint64_t gtt_used = 0;
            std::ifstream gtt_file(device_path + "/mem_info_gtt_used");
            if (gtt_file.is_open()) {
                gtt_file >> gtt_used;
                gtt_file.close();
            }

            // Skip if no memory info found
            if (vram_used == 0 && gtt_used == 0) {
                continue;
            }

            // Calculate memory for this card
            uint64_t card_memory = is_dgpu ? vram_used : (vram_used + gtt_used);

            // Track the GPU with highest utilization
            if (gpu_usage > highest_usage || highest_usage < 0) {
                highest_usage = gpu_usage;
                highest_card = card_name;
                highest_card_memory = card_memory / (1024.0 * 1024.0 * 1024.0); // Convert to GB
            }
        }

        return highest_card_memory > 0 ? highest_card_memory : -1.0;
    } catch (...) {
        return -1.0;
    }

#else
    // VRAM monitoring not implemented for Windows/macOS
    return -1.0;
#endif
}

// Helper: Get NPU utilization (AMD NPU on Linux)
double Server::get_npu_utilization() {
#ifdef __linux__
    try {
        std::string accel_path = "/dev/accel/accel0";
        if (!fs::exists(accel_path)) {
            return -1.0;
        }

        int fd = open(accel_path.c_str(), O_RDWR);
        if (fd < 0) {
            return -1.0;
        }

        // Check DRM API version (must be 0.7 or later for these IOCTLs)
        struct drm_version drm_v;
        memset(&drm_v, 0, sizeof(drm_v));
        bool version_ok = false;
        if (ioctl(fd, DRM_IOCTL_VERSION, &drm_v) == 0) {
            if (drm_v.version_major > 0 || (drm_v.version_major == 0 && drm_v.version_minor >= 7)) {
                version_ok = true;
            }
        }

        if (!version_ok) {
            close(fd);
            return -1.0;
        }

        // Check power_state to avoid waking the NPU if it is asleep
        fs::path power_state_path = "/sys/class/accel/accel0/device/power_state";
        if (fs::exists(power_state_path)) {
            std::ifstream power_file(power_state_path);
            std::string state;
            if (power_file >> state) {
                if (state != "D0") {
                    close(fd);
                    return 0.0;
                }
            }
        }

        amdxdna_drm_query_sensor sensors[16] = {};
        amdxdna_drm_get_info get_info = {};
        get_info.param = DRM_AMDXDNA_QUERY_SENSORS;
        get_info.buffer_size = sizeof(sensors);
        get_info.buffer = (uintptr_t)sensors;

        if (ioctl(fd, DRM_IOCTL_AMDXDNA_GET_INFO, &get_info) < 0) {
            close(fd);
            return -1.0;
        }

        close(fd);

        int num_sensors = get_info.buffer_size / sizeof(amdxdna_drm_query_sensor);
        double usage_sum = 0.0;
        int usage_count = 0;
        for (int i = 0; i < num_sensors; ++i) {
            if (sensors[i].type == AMDXDNA_SENSOR_TYPE_COLUMN_UTILIZATION) {
                double val = (double)sensors[i].input * std::pow(10.0, sensors[i].unitm);
                usage_sum += val;
                usage_count++;
            }
        }

        if (usage_count > 0) {
            // Return average utilization percentage [0, 100]
            return (usage_sum / usage_count);
        }

        return -1.0;
    } catch (...) {
        return -1.0;
    }
#else
    // NPU monitoring not implemented for Windows/macOS
    return -1.0;
#endif
}

void Server::handle_system_stats(const httplib::Request& req, httplib::Response& res) {
    // For HEAD requests, just return 200 OK without processing
    if (req.method == "HEAD") {
        res.status = 200;
        return;
    }

    nlohmann::json stats;

    // CPU usage
    double cpu_percent = get_cpu_usage();
    stats["cpu_percent"] = (cpu_percent >= 0) ? nlohmann::json(cpu_percent) : nlohmann::json();

    // Get memory info
#ifdef _WIN32
    MEMORYSTATUSEX memInfo;
    memInfo.dwLength = sizeof(MEMORYSTATUSEX);
    if (GlobalMemoryStatusEx(&memInfo)) {
        double used_gb = (memInfo.ullTotalPhys - memInfo.ullAvailPhys) / (1024.0 * 1024.0 * 1024.0);
        stats["memory_gb"] = std::round(used_gb * 10.0) / 10.0;
    } else {
        stats["memory_gb"] = 0;
    }
#elif defined(__linux__)
    // Linux: Read /proc/meminfo
    std::ifstream meminfo("/proc/meminfo");
    if (meminfo.is_open()) {
        std::string line;
        long long total_kb = 0, available_kb = 0;
        while (std::getline(meminfo, line)) {
            if (line.find("MemTotal:") == 0) {
                sscanf(line.c_str(), "MemTotal: %lld kB", &total_kb);
            } else if (line.find("MemAvailable:") == 0) {
                sscanf(line.c_str(), "MemAvailable: %lld kB", &available_kb);
                break;
            }
        }
        meminfo.close();
        double used_gb = (total_kb - available_kb) / (1024.0 * 1024.0);
        stats["memory_gb"] = std::round(used_gb * 10.0) / 10.0;
    } else {
        stats["memory_gb"] = 0;
    }
#elif defined(__APPLE__)
    // macOS: Get memory info
    int64_t physical_memory = 0;
    size_t length = sizeof(physical_memory);
    if (sysctlbyname("hw.memsize", &physical_memory, &length, nullptr, 0) == 0) {
        // For now, just report total memory since getting free memory is complex on macOS
        double total_gb = physical_memory / (1024.0 * 1024.0 * 1024.0);
        stats["memory_gb"] = std::round(total_gb * 10.0) / 10.0;
    } else {
        stats["memory_gb"] = 0;
    }
#else
    stats["memory_gb"] = 0;
#endif

    // GPU usage
    double gpu_percent = get_gpu_usage();
    stats["gpu_percent"] = (gpu_percent >= 0) ? nlohmann::json(gpu_percent) : nlohmann::json();

    // VRAM usage
    double vram_gb = get_vram_usage();
    stats["vram_gb"] = (vram_gb >= 0) ? nlohmann::json(vram_gb) : nlohmann::json();

    // NPU Utilization
    double npu_percent = get_npu_utilization();
    stats["npu_percent"] = (npu_percent >= 0) ? nlohmann::json(npu_percent) : nlohmann::json();

    res.set_content(stats.dump(), "application/json");
}

void Server::handle_log_level(const httplib::Request& req, httplib::Response& res) {
    try {
        auto request_json = nlohmann::json::parse(req.body);

        // Translate {"level":"debug"} -> config_->set({"log_level":"debug"})
        json changes = {{"log_level", request_json["level"]}};
        config_->set(changes, [this](const json& applied) {
            apply_config_side_effects(applied);
        });

        // Return same response format for backward compatibility
        nlohmann::json response = {{"status", "success"}, {"level", config_->log_level()}};
        res.set_content(response.dump(), "application/json");
    } catch (const std::invalid_argument& e) {
        res.status = 400;
        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_log_level: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_shutdown(const httplib::Request& req, httplib::Response& res) {
    LOG(INFO, "Server") << "Shutdown request received" << std::endl;

    // Unload all models SYNCHRONOUSLY before sending the response.
    // This ensures child processes (llama-server, etc.) are terminated
    // before the caller proceeds, avoiding zombie processes.
    if (router_) {
        LOG(INFO, "Server") << "Unloading models and stopping backend servers..." << std::endl;
        try {
            router_->unload_model();
            LOG(INFO, "Server") << "All models unloaded" << std::endl;
        } catch (const std::exception& e) {
            LOG(ERROR, "Server") << "Error during unload: " << e.what() << std::endl;
        }
    }

    nlohmann::json response = {{"status", "shutting down"}};
    res.set_content(response.dump(), "application/json");

    // Stop the HTTP listener and exit asynchronously (allows response to be sent first)
    std::thread([this]() {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        stop();
        std::exit(0);
    }).detach();
}

void Server::handle_config_set(const httplib::Request& req, httplib::Response& res) {
    try {
        auto body = nlohmann::json::parse(req.body);

        auto result = config_->set(body, [this](const json& applied) {
            apply_config_side_effects(applied);
        });

        // Persist changes to config.json
        if (!cache_dir_.empty()) {
            try {
                ConfigFile::save(cache_dir_, config_->snapshot());
            } catch (const std::exception& e) {
                LOG(WARNING, "Server") << "Failed to persist config.json: " << e.what() << std::endl;
            }
        }

        res.set_content(result.dump(), "application/json");
    } catch (const nlohmann::json::parse_error& e) {
        res.status = 400;
        nlohmann::json error = {{"error", "Invalid JSON in request body"}};
        res.set_content(error.dump(), "application/json");
    } catch (const std::invalid_argument& e) {
        res.status = 400;
        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_config_set: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_config_get(const httplib::Request& /*req*/, httplib::Response& res) {
    try {
        auto snap = config_->snapshot();
        res.set_content(snap.dump(), "application/json");
    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_config_get: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_bin_change(const std::string& section,
                                const std::string& bin_key,
                                const std::string& new_value) {
    std::string recipe = RuntimeConfig::config_section_to_recipe(section);

    // bin_key is "<backend>_bin" — strip the suffix to get the backend name
    // expected by install_backend / find_external_backend_binary.
    std::string backend = bin_key.substr(0, bin_key.size() - 4);

    // The "server_bin" key (as in ryzenai.server_bin) is not consumed by the
    // current install flow — find_external_backend_binary uses recipe-based
    // section lookup and there is no recipe whose section equals "ryzenai".
    // Skip the hot-swap rather than attempt an install that won't help.
    if (backend == "server") {
        LOG(WARNING, "Server") << section << "." << bin_key
                               << " is not consumed by the install flow; "
                                  "no hot-swap performed." << std::endl;
        return;
    }

    LOG(INFO, "Server") << "*_bin config changed: " << section << "." << bin_key
                        << " = '" << new_value << "' — hot-swapping "
                        << recipe << ":" << backend << std::endl;

    // Snapshot loaded models on this (recipe, backend). A model whose options
    // do not pin a backend (mb empty) is treated as potentially affected since
    // it could resolve to the changed backend on next load.
    struct Saved {
        std::string name;
        RecipeOptions opts;
    };
    std::vector<Saved> previously_loaded;
    auto loaded = router_->get_all_loaded_models();
    std::string backend_option_key = recipe + "_backend";
    for (const auto& m : loaded) {
        if (m.value("recipe", "") != recipe) continue;
        std::string mb;
        if (m.contains("recipe_options") && m["recipe_options"].contains(backend_option_key)) {
            mb = m["recipe_options"].value(backend_option_key, "");
        }
        if (!mb.empty() && mb != backend) continue;
        std::string name = m.value("model_name", "");
        if (name.empty()) continue;
        previously_loaded.push_back({name, router_->get_model_recipe_options(name)});
    }

    for (const auto& s : previously_loaded) {
        LOG(INFO, "Server") << "Unloading " << s.name
                            << " before installing new " << recipe << ":" << backend
                            << " binary" << std::endl;
        try {
            router_->unload_model(s.name);
        } catch (const std::exception& e) {
            LOG(WARNING, "Server") << "Failed to unload " << s.name << ": " << e.what() << std::endl;
        }
    }

    // Install the new binary. install_from_github bails early when the user's
    // value resolves to a path (find_external_backend_binary returns it). When
    // version.txt mismatches the resolved version, the install dir is wiped
    // and re-downloaded.
    try {
        backend_manager_->install_backend(recipe, backend);
    } catch (const std::exception& e) {
        LOG(WARNING, "Server") << "install_backend(" << recipe << ":" << backend
                               << ") failed after *_bin change: " << e.what() << std::endl;
    }

    // Best-effort reload of previously-loaded models on the new binary.
    for (const auto& s : previously_loaded) {
        try {
            auto info = model_manager_->get_model_info(s.name);
            router_->load_model(s.name, info, s.opts, true);
            LOG(INFO, "Server") << "Reloaded " << s.name << " on new "
                                << recipe << ":" << backend << " binary" << std::endl;
        } catch (const std::exception& e) {
            LOG(WARNING, "Server") << "Failed to reload " << s.name
                                   << " after *_bin hot-swap: " << e.what() << std::endl;
        }
    }

    SystemInfoCache::invalidate_recipes();
    model_manager_->invalidate_models_cache();
}

void Server::apply_config_side_effects(const json& applied_changes) {
    for (auto& [key, value] : applied_changes.items()) {
        if (key == "port") {
            int new_port = config_->port();
            int current_port = port_.load();
            if (new_port != current_port) {
                LOG(INFO, "Server") << "Port change requested: " << current_port << " -> " << new_port << std::endl;
                port_.store(new_port);
                rebind_requested_ = true;
                udp_beacon_.stopBroadcasting();
                http_server_->stop();
                http_server_v6_->stop();
            }
        } else if (key == "host") {
            LOG(INFO, "Server") << "Host change requested to: " << config_->host() << std::endl;
            rebind_requested_ = true;
            udp_beacon_.stopBroadcasting();
            http_server_->stop();
            http_server_v6_->stop();
            // Restart websocket server with new host
            if (websocket_server_) {
                websocket_server_->stop();
                websocket_server_ = std::make_unique<WebSocketServer>(
                    router_.get(),
                    config_->host(),
                    config_->websocket_port());
                if (running_) {
                    websocket_server_->start();
                }
            }
        } else if (key == "websocket_port") {
            if (websocket_server_) {
                LOG(INFO, "Server") << "Restarting WebSocket server on requested port "
                                    << config_->websocket_port() << std::endl;
                websocket_server_->stop();
                websocket_server_ = std::make_unique<WebSocketServer>(
                    router_.get(),
                    config_->host(),
                    config_->websocket_port());
                if (running_) {
                    websocket_server_->start();
                }
            }
        } else if (key == "log_level") {
            std::string level = config_->log_level();
            LOG(INFO, "Server") << "Log level changed to: " << level << std::endl;
            reconfigure_application_logging(level);
        } else if (key == "global_timeout") {
            long timeout = config_->global_timeout();
            LOG(INFO, "Server") << "Global timeout changed to: " << timeout << "s" << std::endl;
            utils::HttpClient::set_default_timeout(timeout);
        } else if (key == "no_broadcast") {
            bool nb = config_->no_broadcast();
            LOG(INFO, "Server") << "Broadcast " << (nb ? "disabled" : "enabled") << std::endl;
            if (nb) {
                udp_beacon_.stopBroadcasting();
            } else {
                auto rfc1918Interfaces = udp_beacon_.getLocalRFC1918Interfaces();
                if (!rfc1918Interfaces.empty()) {
                    udp_beacon_.startBroadcasting(13305, port_, 2);
                }
            }
        } else if (key == "extra_models_dir") {
            std::string dir = config_->extra_models_dir();
            LOG(INFO, "Server") << "Extra models dir changed to: " << dir << std::endl;
            model_manager_->set_extra_models_dir(dir);
        } else if (key == "models_dir") {
            std::string dir = config_->models_dir();
            LOG(INFO, "Server") << "Models dir changed to: " << dir << std::endl;
            utils::set_models_dir(dir);
            model_manager_->invalidate_models_cache();
        } else if (value.is_object()) {
            // Nested backend section change (llamacpp / whispercpp / sdcpp / ryzenai / kokoro).
            // Look for *_bin sub-keys and trigger a hot-swap of the affected backend.
            for (auto& [sub_key, sub_value] : value.items()) {
                if (sub_key.size() >= 4
                    && sub_key.compare(sub_key.size() - 4, 4, "_bin") == 0) {
                    handle_bin_change(key, sub_key,
                                      sub_value.is_string() ? sub_value.get<std::string>() : "");
                }
            }
        }
    }
}

// ============================================================================
// Shared SSE streaming helper for download operations
// ============================================================================

void Server::stream_download_operation(
    httplib::Response& res,
    std::function<void(DownloadProgressCallback)> operation) {

    res.set_header("Cache-Control", "no-cache");
    res.set_header("Connection", "keep-alive");
    res.set_header("X-Accel-Buffering", "no");

    res.set_chunked_content_provider(
        "text/event-stream",
        [operation = std::move(operation)](size_t offset, httplib::DataSink& sink) {
            if (offset > 0) {
                return false; // Already sent everything
            }

            try {
                // Create progress callback that emits SSE events
                bool complete_sent = false;
                DownloadProgressCallback progress_cb = [&sink, &complete_sent](const DownloadProgress& p) -> bool {
                    nlohmann::json event_data;
                    event_data["file"] = p.file;
                    event_data["file_index"] = p.file_index;
                    event_data["total_files"] = p.total_files;
                    event_data["bytes_downloaded"] = static_cast<uint64_t>(p.bytes_downloaded);
                    event_data["bytes_total"] = static_cast<uint64_t>(p.bytes_total);
                    event_data["total_download_size"] = static_cast<uint64_t>(p.total_download_size);
                    event_data["bytes_previously_downloaded"] = static_cast<uint64_t>(p.bytes_previously_downloaded);
                    event_data["percent"] = p.percent;

                    std::string event;
                    if (p.complete) {
                        event = "event: complete\ndata: " + event_data.dump() + "\n\n";
                        complete_sent = true;
                    } else {
                        event = "event: progress\ndata: " + event_data.dump() + "\n\n";
                    }

                    if (!sink.write(event.c_str(), event.size())) {
                        LOG(INFO, "Server") << "Client disconnected, cancelling download" << std::endl;
                        return false;
                    }
                    return true;
                };

                operation(progress_cb);

                // If operation completed without sending a "complete" event
                // (e.g. backend was already installed), send one now
                if (!complete_sent) {
                    nlohmann::json done_data = {{"status", "ok"}};
                    std::string event = "event: complete\ndata: " + done_data.dump() + "\n\n";
                    sink.write(event.c_str(), event.size());
                }

            } catch (const lemon::UnknownModelError& e) {
                nlohmann::json error_data = {{"error", e.what()}, {"code", lemon::kUnknownModelErrorCode}};
                std::string event = "event: error\ndata: " + error_data.dump() + "\n\n";
                sink.write(event.c_str(), event.size());
            } catch (const std::exception& e) {
                std::string error_msg = e.what();
                if (error_msg != "Download cancelled") {
                    nlohmann::json error_data = {{"error", error_msg}};
                    std::string event = "event: error\ndata: " + error_data.dump() + "\n\n";
                    sink.write(event.c_str(), event.size());
                }
            }

            sink.done();
            return false;
        });
}

// ============================================================================
// Backend management endpoints
// ============================================================================

void Server::handle_install(const httplib::Request& req, httplib::Response& res) {
    try {
        auto request_json = nlohmann::json::parse(req.body);

        std::string recipe = request_json.value("recipe", "");
        std::string backend = request_json.value("backend", "");
        bool stream = request_json.value("stream", false);
        bool force = request_json.value("force", false);

        if (recipe.empty() || backend.empty()) {
            res.status = 400;
            nlohmann::json error = {{"error", "Both 'recipe' and 'backend' are required"}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        LOG(INFO, "Server") << "Installing backend: " << recipe << ":" << backend << std::endl;

        // Get fresh state before any checks
        SystemInfoCache::invalidate_recipes();

        // Check if this backend requires manual setup (e.g. FLM on Linux).
        // If so, return the action URL instead of attempting installation.
        json system_info = SystemInfoCache::get_system_info_with_cache();
        if (system_info.contains("recipes") &&
            system_info["recipes"].contains(recipe) &&
            system_info["recipes"][recipe].contains("backends") &&
            system_info["recipes"][recipe]["backends"].contains(backend)) {
            const auto& backend_info = system_info["recipes"][recipe]["backends"][backend];
            std::string state = backend_info.value("state", "unsupported");
            std::string message = backend_info.value("message", "Backend is not supported on this system.");
            std::string action = backend_info.value("action", "");

            if (state == "unsupported" && !force) {
                res.status = 400;
                nlohmann::json error = {
                    {"error", "Cannot install " + recipe + ":" + backend + " on this system: " + message},
                    {"recipe", recipe},
                    {"backend", backend}
                };
                res.set_content(error.dump(), "application/json");
                return;
            }

            if (action.find(".html") != std::string::npos) {
                auto url_pos = action.find("https://");
                if (url_pos != std::string::npos) {
                    nlohmann::json response = {
                        {"action", action.substr(url_pos)},
                        {"recipe", recipe},
                        {"backend", backend}
                    };
                    res.set_content(response.dump(), "application/json");
                    return;
                }
            }
        }

        if (stream) {
            stream_download_operation(res, [this, recipe, backend, force](DownloadProgressCallback progress_cb) {
                backend_manager_->install_backend(recipe, backend, force, progress_cb);
                SystemInfoCache::invalidate_recipes();
                model_manager_->invalidate_models_cache();
            });
        } else {
            backend_manager_->install_backend(recipe, backend, force);
            SystemInfoCache::invalidate_recipes();
            model_manager_->invalidate_models_cache();
            nlohmann::json response = {
                {"status", "success"},
                {"recipe", recipe},
                {"backend", backend}
            };
            res.set_content(response.dump(), "application/json");
        }

    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_install: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::handle_uninstall(const httplib::Request& req, httplib::Response& res) {
    try {
        auto request_json = nlohmann::json::parse(req.body);
        std::string recipe = request_json.value("recipe", "");
        std::string backend = request_json.value("backend", "");

        if (recipe.empty() || backend.empty()) {
            res.status = 400;
            nlohmann::json error = {{"error", "Both 'recipe' and 'backend' are required"}};
            res.set_content(error.dump(), "application/json");
            return;
        }

        LOG(INFO, "Server") << "Uninstalling backend: " << recipe << ":" << backend << std::endl;

        // Check if any loaded models use this recipe+backend and unload them first
        auto loaded_models = router_->get_all_loaded_models();
        std::string backend_option_key = recipe + "_backend";
        for (const auto& model : loaded_models) {
            if (model.value("recipe", "") == recipe) {
                // Check if the model's backend matches the one being uninstalled
                std::string model_backend;
                if (model.contains("recipe_options") && model["recipe_options"].contains(backend_option_key)) {
                    model_backend = model["recipe_options"].value(backend_option_key, "");
                }
                if (!model_backend.empty() && model_backend != backend) {
                    continue;  // Different backend, skip
                }
                std::string model_name = model.value("model_name", "");
                LOG(INFO, "Server") << "Unloading model " << model_name
                          << " before uninstalling " << recipe << ":" << backend << std::endl;
                router_->unload_model(model_name);
            }
        }

        backend_manager_->uninstall_backend(recipe, backend);

        SystemInfoCache::invalidate_recipes();
        model_manager_->invalidate_models_cache();

        nlohmann::json response = {
            {"status", "success"},
            {"recipe", recipe},
            {"backend", backend}
        };
        res.set_content(response.dump(), "application/json");

    } catch (const std::exception& e) {
        LOG(ERROR, "Server") << "ERROR in handle_uninstall: " << e.what() << std::endl;
        res.status = 500;
        nlohmann::json error = {{"error", e.what()}};
        res.set_content(error.dump(), "application/json");
    }
}

void Server::enrich_recipes(json& recipes) {
    if (!backend_manager_) return;

    for (auto& [recipe_name, recipe_info] : recipes.items()) {
        if (!recipe_info.contains("backends")) continue;
        for (auto& [backend_name, backend_info] : recipe_info["backends"].items()) {
            try {
                auto enrichment = backend_manager_->get_backend_enrichment(recipe_name, backend_name);
                if (!enrichment.release_url.empty()) {
                    backend_info["release_url"] = enrichment.release_url;
                }
                if (!enrichment.download_filename.empty()) {
                    backend_info["download_filename"] = enrichment.download_filename;
                }
                if (!backend_info.contains("version") || backend_info["version"].get<std::string>().empty()) {
                    if (!enrichment.version.empty()) {
                        backend_info["version"] = enrichment.version;
                    }
                }
            } catch (...) {}
        }
    }
}

} // namespace lemon
