#include "lemon/system_info.h"
#include "lemon/runtime_config.h"
#include "lemon/version.h"
#include "lemon/utils/path_utils.h"
#include "lemon/utils/version_utils.h"
#include "lemon/utils/json_utils.h"
#include "lemon/utils/process_manager.h"
#include "lemon/backends/backend_utils.h"
#include <filesystem>
#include <fstream>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <regex>
#include <lemon/utils/aixlog.hpp>
#include <algorithm>
#include <cctype>
#include <set>
#include <map>
#include <mutex>
#include <vector>
#include <cmath>

#ifdef _WIN32
#include <windows.h>
#include <comdef.h>
#include <Wbemidl.h>
#include <intrin.h>
#include "utils/wmi_helper.h"
#pragma comment(lib, "wbemuuid.lib")
#endif

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

#ifdef __linux__
#include <unistd.h>
#endif

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

#ifndef _WIN32
#include <sys/wait.h>
#endif

#ifdef HAVE_SYSTEMD
#include <systemd/sd-login.h>
#endif

namespace lemon {

namespace fs = std::filesystem;
using namespace lemon::utils;
using namespace lemon::backends;

// AMD discrete GPU keywords
const std::vector<std::string> AMD_DISCRETE_GPU_KEYWORDS = {
    "rx ", "xt", "pro w", "pro v", "radeon pro", "firepro", "fury"
};

// NVIDIA discrete GPU keywords
const std::vector<std::string> NVIDIA_DISCRETE_GPU_KEYWORDS = {
    "geforce", "rtx", "gtx", "quadro", "tesla", "titan",
    "a100", "a40", "a30", "a10", "a6000", "a5000", "a4000", "a2000"
};

// ROCm architecture mapping - maps specific gfx architectures to their family
const std::map<std::string, std::string> ROCM_ARCH_MAPPING = {
    // RDNA2 family (gfx103X)
    {"gfx1030", "gfx103X"},
    {"gfx1031", "gfx103X"},
    {"gfx1032", "gfx103X"},
    {"gfx1034", "gfx103X"},
    // Note: gfx1033, gfx1035, gfx1036 are NOT included (not confirmed as supported)

    // RDNA3 family (gfx110X)
    {"gfx1100", "gfx110X"},
    {"gfx1101", "gfx110X"},
    {"gfx1102", "gfx110X"},
    {"gfx1103", "gfx110X"},

    // RDNA3.5 iGPUs - explicit binary names (no family mapping)
    {"gfx1150", "gfx1150"},  // Maps to exact binary name
    {"gfx1151", "gfx1151"},  // Maps to exact binary name

    // RDNA4 family (gfx120X)
    {"gfx1200", "gfx120X"},
    {"gfx1201", "gfx120X"},
};

// ============================================================================
// Recipe/Backend definition table - single source of truth for support matrix
// ============================================================================

// Device constraints: device_type -> set of allowed families (empty = all families)
using DeviceConstraints = std::map<std::string, std::set<std::string>>;

struct RecipeBackendDef {
    std::string recipe;
    std::string backend;
    std::set<std::string> supported_os;
    DeviceConstraints devices;
};

// Recipe definitions table - single source of truth for all recipe/backend support
// Format: {recipe, backend, {supported_os}, {{device_type, {allowed_families}}}}
//
// IMPORTANT: Backend order matters! For recipes with multiple backends (e.g., llamacpp),
// the order in this table defines the preference order. First listed = most preferred.
// Example: metal is listed before vulkan on macOS, vulkan before cpu elsewhere.
//
// Empty family set {} means "all families of that device type"
static const std::vector<RecipeBackendDef> RECIPE_DEFS = {
    // llamacpp with multiple backends (order = preference: system > metal > vulkan > rocm > cpu)
    {"llamacpp", "system", {"linux"}, {
        {"cpu", {"x86_64"}}, // Placeholder, actual check is PATH-based
    }},
    {"llamacpp", "metal", {"macos"},
    {
        {"metal", {}},
    }},
    {"llamacpp", "vulkan", {"windows", "linux"}, {
        {"cpu", {"x86_64"}},
        {"amd_igpu", {}},      // all iGPU families
        {"amd_dgpu", {}},      // all dGPU families
    }},
    {"llamacpp", "rocm", {"windows", "linux"}, {
        {"amd_igpu", {"gfx1150", "gfx1151"}},                      // STX Point/Halo iGPUs (explicit binaries)
        {"amd_dgpu", {"gfx103X", "gfx110X", "gfx120X"}},          // RDNA2/3/4 dGPUs (family binaries)
    }},
    {"llamacpp", "cpu", {"windows", "linux"}, {
        {"cpu", {"x86_64"}},
    }},

    // whisper.cpp - Windows: NPU and CPU; Linux: CPU and Vulkan only
    {"whispercpp", "npu", {"windows"}, {
        {"amd_npu", {"XDNA2"}},
    }},
    {"whispercpp", "vulkan", {"linux"}, {
        {"cpu", {"x86_64"}},
    }},
    {"whispercpp", "cpu", {"windows", "linux"}, {
        {"cpu", {"x86_64"}},
    }},

    // kokoro - Windows/Linux x86_64
    {"kokoro", "cpu", {"windows", "linux"}, {
        {"cpu", {"x86_64"}},
    }},

    // stable-diffusion.cpp - ROCm backend for AMD GPUs
    {"sd-cpp", "rocm", {"windows", "linux"}, {
        {"amd_igpu", {
#ifdef __linux__
            "gfx1150",   // Strix Point - Linux only (ROCm not yet supported on Windows)
#endif
            "gfx1151"
        }},
        {"amd_dgpu", {"gfx103X", "gfx110X", "gfx120X"}},
    }},

    // stable-diffusion.cpp - CPU backend (Windows/Linux x86_64)
    {"sd-cpp", "cpu", {"windows", "linux"}, {
        {"cpu", {"x86_64"}},
    }},

    // FLM - NPU (XDNA2)
    {"flm", "npu", {"windows", "linux"}, {
        {"amd_npu", {"XDNA2"}},
    }},

    // RyzenAI LLM - Windows NPU (XDNA2)
    {"ryzenai-llm", "npu", {"windows"}, {
        {"amd_npu", {"XDNA2"}},
    }},
};

// ============================================================================
// Device family to human-readable name mapping
// ============================================================================

// Maps device family codes to human-readable descriptions
// Format: {family_code, human_readable_name}
static const std::map<std::string, std::string> DEVICE_FAMILY_NAMES = {
    // CPU architectures
    {"x86_64", "x86-64 processors"},
    {"arm64", "ARM64 processors"},

    // AMD iGPU/dGPU architectures (ROCm)
    {"gfx1150", "Radeon 880M/890M (Strix Point)"},
    {"gfx1151", "Radeon 8050S/8060S (Strix Halo)"},
    {"gfx103X", "Radeon RX 6000 series (RDNA2)"},
    {"gfx110X", "Radeon RX 7000 series (RDNA3)"},
    {"gfx120X", "Radeon RX 9000 series (RDNA4)"},

    // NPU architectures
    {"XDNA2", "AMD XDNA 2"},
};

// Maps device types to human-readable names (for error messages)
static const std::map<std::string, std::string> DEVICE_TYPE_NAMES = {
    {"cpu", "CPU"},
    {"amd_igpu", "AMD iGPU"},
    {"amd_dgpu", "AMD dGPU"},
    {"amd_npu", "AMD NPU"},
    {"nvidia_dgpu", "NVIDIA GPU"},
    {"metal", "MacOS Metal GPU"}
};

// Get human-readable name for a device family (e.g., "gfx1150" -> "Radeon 880M/890M")
static std::string get_family_name(const std::string& family) {
    auto it = DEVICE_FAMILY_NAMES.find(family);
    return it != DEVICE_FAMILY_NAMES.end() ? it->second : family;
}

// Get human-readable name for a device type (e.g., "amd_igpu" -> "AMD iGPU")
static std::string get_device_type_name(const std::string& device_type) {
    auto it = DEVICE_TYPE_NAMES.find(device_type);
    return it != DEVICE_TYPE_NAMES.end() ? it->second : device_type;
}

// Generate a human-readable error message for unsupported backend
// Uses RECIPE_DEFS and DEVICE_FAMILY_NAMES to build a descriptive message
std::string SystemInfo::get_unsupported_backend_error(const std::string& recipe, const std::string& backend) {
    std::string error;

    // Find the recipe/backend in RECIPE_DEFS
    for (const auto& def : RECIPE_DEFS) {
        if (def.recipe == recipe && def.backend == backend) {
            // Collect all required family names
            std::vector<std::string> family_names;
            for (const auto& [device_type, families] : def.devices) {
                for (const auto& f : families) {
                    family_names.push_back(get_family_name(f));
                }
            }

            // Build error message
            error = "No compatible device detected for " + recipe;
            error += " (" + backend + " backend)";
            if (!family_names.empty()) {
                error += ". Requires: ";
                for (size_t i = 0; i < family_names.size(); i++) {
                    if (i > 0) error += ", ";
                    error += family_names[i];
                }
            }
            error += ".";
            break;
        }
    }

    if (error.empty()) {
        error = "Unsupported recipe/backend combination: " + recipe + "/" + backend;
    }

    return error;
}

// Detected device with its family
struct DetectedDevice {
    std::string type;      // "cpu", "amd_igpu", "amd_dgpu", "amd_npu"
    std::string name;      // Full device name
    std::string family;    // "x86_64", "gfx1150", "XDNA2", etc.
    bool present;
};

// Get current OS identifier
static std::string get_current_os() {
    #ifdef _WIN32
    return "windows";
    #elif defined(__APPLE__)
    return "macos";
    #else
    return "linux";
    #endif
}

// Forward declarations for helper functions
std::string identify_rocm_arch_from_name(const std::string& device_name);
std::string identify_npu_arch();
static std::string read_version_file(const fs::path& version_file);
static std::string get_expected_backend_version(const std::string& recipe, const std::string& backend);

// Check if device matches constraints (empty constraint set = all families allowed)
static bool device_matches_constraint(const std::string& device_family,
                                       const std::set<std::string>& allowed_families) {
    if (allowed_families.empty()) {
        return true;  // Empty = all families allowed
    }
    return allowed_families.count(device_family) > 0;
}

// Generic installation check
static bool is_recipe_installed(const std::string& recipe, const std::string& backend, std::string& error_message) {
    // Special handling for ROCm backends on gfx1151 (Strix Halo) if kernel CWSR fix is missing
    if ((recipe == "llamacpp" || recipe == "sd-cpp") && backend == "rocm") {
        if (needs_gfx1151_cwsr_fix()) {
            error_message = "Linux kernel missing support";
            return false;
        }
    }
    auto* spec = try_get_spec_for_recipe(recipe);
    if (spec) {
        try {
            BackendUtils::get_backend_binary_path(*spec, backend);

            // For system llamacpp backend, also verify the HIP plugin is available
            // This is required for ROCm GPU acceleration with dynamically loaded backends
            if (recipe == "llamacpp" && backend == "system") {
#ifdef __linux__
                // Check if AMD GPU driver is loaded (KFD indicates amdgpu driver)
                if (fs::exists("/sys/class/kfd")) {
                    // System has AMD GPU(s), so we need the HIP plugin
                    if (!is_ggml_hip_plugin_available()) {
                        error_message = "HIP plugin libggml-hip.so not installed";
                        return false;
                    }
                }
#endif
            }

            return true;
        } catch (...) {
#ifndef _WIN32
            // On Linux, FLM is installed as a system package (in PATH, not install dir)
            if (recipe == "flm" && !utils::find_flm_executable().empty()) {
                return true;
            }
#endif
            return false;
        }
    }
    return false;
}

static std::string get_recipe_version(const std::string& recipe, const std::string& backend) {
    if (recipe == "llamacpp" && backend == "system") {
        return SystemInfo::get_system_llamacpp_version();
    }
    auto* spec = try_get_spec_for_recipe(recipe);
    if (spec) {
        std::string version_file = BackendUtils::get_installed_version_file(*spec, backend);
        if (version_file.empty()) {
#ifndef _WIN32
            // On Linux, FLM is a system package with no version.txt - query directly
            if (recipe == "flm") {
                return SystemInfo::get_flm_version();
            }
#endif
            return "unknown";
        }
        std::string version = read_version_file(version_file);
#ifndef _WIN32
        // On Linux, version.txt may not exist on disk for system-installed FLM
        if (recipe == "flm" && (version.empty() || version == "unknown")) {
            return SystemInfo::get_flm_version();
        }
#endif
        return version;
    }
    return "";
}

static std::string get_install_command(const std::string& recipe, const std::string& backend) {
    return "lemonade backends install " + recipe + ":" + backend;
}

static std::string get_expected_backend_version(const std::string& recipe, const std::string& backend) {
    static json backend_versions = []() -> json {
        try {
            std::string config_path = utils::get_resource_path("resources/backend_versions.json");
            std::ifstream file(config_path);
            if (!file.is_open()) {
                return json::object();
            }
            json data = json::parse(file);
            file.close();
            return data;
        } catch (...) {
            return json::object();
        }
    }();

    if (!backend_versions.contains(recipe)) {
        return "";
    }
    const auto& recipe_config = backend_versions[recipe];
    if (!recipe_config.contains(backend) || !recipe_config[backend].is_string()) {
        return "";
    }
    return recipe_config[backend].get<std::string>();
}

// ============================================================================
// SystemInfo base class implementation
// ============================================================================

json SystemInfo::get_system_info_dict() {
    json info;
    info["OS Version"] = get_os_version();
    return info;
}

json SystemInfo::get_device_dict() {
    json devices;

    // NOTE: This function collects hardware info only (no inference engines).
    // Inference engines are detected separately in get_system_info_with_cache()
    // because they should always be fresh (not cached).

    // Get CPU info - with fault tolerance
    try {
        auto cpu = get_cpu_device();
        devices["cpu"] = {
            {"name", cpu.name},
            {"cores", cpu.cores},
            {"threads", cpu.threads},
            {"available", cpu.available}
        };
        #if defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)
        devices["cpu"]["family"] = "x86_64";
        #elif defined(__aarch64__) || defined(_M_ARM64)
        devices["cpu"]["family"] = "arm64";
        #else
        devices["cpu"]["family"] = "unknown";
        #endif
        if (!cpu.error.empty()) {
            devices["cpu"]["error"] = cpu.error;
        }
    } catch (const std::exception& e) {
        devices["cpu"] = {
            {"name", "Unknown"},
            {"cores", 0},
            {"threads", 0},
            {"available", true},  // Assume available - trust the user
            {"error", std::string("Detection exception: ") + e.what()}
        };
    }

    // Get AMD iGPU info - with fault tolerance
    try {
        auto amd_igpu = get_amd_igpu_device();
        devices["amd_igpu"] = {
            {"name", amd_igpu.name},
            {"vram_gb", amd_igpu.vram_gb},
            {"virtual_mem_gb", amd_igpu.virtual_gb},
            {"available", amd_igpu.available}
        };
        devices["amd_igpu"]["family"] = identify_rocm_arch_from_name(amd_igpu.name);
        if (!amd_igpu.error.empty()) {
            devices["amd_igpu"]["error"] = amd_igpu.error;
        }
    } catch (const std::exception& e) {
        devices["amd_igpu"] = {
            {"name", "Unknown"},
            {"available", true},  // Assume available - trust the user
            {"error", std::string("Detection exception: ") + e.what()}
        };
    }

    // Get AMD dGPU info - with fault tolerance
    try {
        auto amd_dgpus = get_amd_dgpu_devices();
        devices["amd_dgpu"] = json::array();
        for (const auto& gpu : amd_dgpus) {
            json gpu_json = {
                {"name", gpu.name},
                {"available", gpu.available}
            };
            if (gpu.vram_gb > 0) {
                gpu_json["vram_gb"] = gpu.vram_gb;
            }
            if (gpu.virtual_gb > 0) {
                gpu_json["virtual_mem_gb"] = gpu.virtual_gb;
            }
            if (!gpu.driver_version.empty()) {
                gpu_json["driver_version"] = gpu.driver_version;
            }
            gpu_json["family"] = identify_rocm_arch_from_name(gpu.name);
            if (!gpu.error.empty()) {
                gpu_json["error"] = gpu.error;
            }
            devices["amd_dgpu"].push_back(gpu_json);
        }
    } catch (const std::exception& e) {
        devices["amd_dgpu"] = json::array();
        devices["amd_dgpu_error"] = std::string("Detection exception: ") + e.what();
    }

    // Get NVIDIA dGPU info - with fault tolerance
    try {
        auto nvidia_dgpus = get_nvidia_dgpu_devices();
        devices["nvidia_dgpu"] = json::array();
        for (const auto& gpu : nvidia_dgpus) {
            json gpu_json = {
                {"name", gpu.name},
                {"available", gpu.available}
            };
            if (gpu.vram_gb > 0) {
                gpu_json["vram_gb"] = gpu.vram_gb;
            }
            if (!gpu.driver_version.empty()) {
                gpu_json["driver_version"] = gpu.driver_version;
            }
            if (!gpu.error.empty()) {
                gpu_json["error"] = gpu.error;
            }
            devices["nvidia_dgpu"].push_back(gpu_json);
        }
    } catch (const std::exception& e) {
        devices["nvidia_dgpu"] = json::array();
        devices["nvidia_dgpu_error"] = std::string("Detection exception: ") + e.what();
    }

    // Get NPU info - with fault tolerance
    // Use CPU processor name as the NPU device name (e.g., "AMD Ryzen AI 9 HX 375")
    try {
        auto npu = get_npu_device();
        std::string cpu_name = devices.contains("cpu") ? devices["cpu"].value("name", "") : "";
        devices["amd_npu"] = {
            {"name", cpu_name.empty() ? npu.name : cpu_name},
            {"available", npu.available}
        };
        devices["amd_npu"]["family"] = identify_npu_arch();
        if (npu.tops_max > 0) {
            devices["amd_npu"]["tops_max_int"] = npu.tops_max;
        }
        devices["amd_npu"]["utilization"] = npu.utilization;
        if (!npu.power_mode.empty()) {
            devices["amd_npu"]["power_mode"] = npu.power_mode;
        }
        if (!npu.error.empty()) {
            devices["amd_npu"]["error"] = npu.error;
        }
    } catch (const std::exception& e) {
        #ifdef _WIN32
        // On Windows, assume NPU may be available - trust the user
        devices["amd_npu"] = {
            {"name", "Unknown"},
            {"available", true},
            {"error", std::string("Detection exception: ") + e.what()}
        };
        #else
        devices["amd_npu"] = {
            {"name", "Unknown"},
            {"available", false},
            {"error", std::string("Detection exception: ") + e.what()}
        };
        #endif
    }

    #ifdef __APPLE__
    // Get Metal GPU info (macOS only) - with fault tolerance
    try {
        auto* mac_info = dynamic_cast<MacOSSystemInfo*>(this);

        if (mac_info) {
            auto metal_gpus = dynamic_cast<MacOSSystemInfo*>(this)->detect_metal_gpus();
            if (!metal_gpus.empty() && metal_gpus[0].available) {
                // Use first available Metal GPU (similar to how single devices are handled)
                const auto& gpu = metal_gpus[0];
                devices["metal"] = {
                    {"name", gpu.name},
                    {"available", gpu.available}
                };
                if (gpu.vram_gb > 0) {
                    devices["metal"]["vram_gb"] = gpu.vram_gb;
                }
                if (!gpu.driver_version.empty()) {
                    devices["metal"]["driver_version"] = gpu.driver_version;
                }
                devices["metal"]["family"] = "metal";
                if (!gpu.error.empty()) {
                    devices["metal"]["error"] = gpu.error;
                }
            } else {
                devices["metal"] = {
                    {"name", "Unknown"},
                    {"available", false},
                    {"error", "No Metal-compatible GPU found"}
                };
            }
        }
        else {
            devices["metal"] = {
                {"name", "Unknown"},
                {"available", false},
                {"error", std::string("Detection exception: ")}
            };
        }
    } catch (const std::exception& e) {
        devices["metal"] = {
            {"name", "Unknown"},
            {"available", false},
            {"error", std::string("Detection exception: ") + e.what()}
        };
    }
    #endif

    return devices;
}

std::string SystemInfo::get_os_version() {
    // Platform-specific implementation would go here
    // For now, return a basic string
    #ifdef _WIN32
    return "Windows";
    #elif __linux__
    return "Linux";
    #elif __APPLE__
    return "macOS";
    #else
    return "Unknown";
    #endif
}

json SystemInfo::build_recipes_info(const json& devices) {
    json recipes;

    // Get current OS
    std::string current_os = get_current_os();

    std::vector<DetectedDevice> detected_devices;

    // Build detected_devices from devices JSON, reading cached "family" fields
    // CPU is always present
    if (devices.contains("cpu")) {
        const auto& cpu = devices["cpu"];
        std::string name = cpu.value("name", "CPU");
        std::string family = cpu.value("family", "");
        detected_devices.push_back({"cpu", name, family, true});
    } else {
        detected_devices.push_back({"cpu", "CPU", "", true});
    }

    // AMD iGPU
    if (devices.contains("amd_igpu") && devices["amd_igpu"].is_object()) {
        const auto& igpu = devices["amd_igpu"];
        if (igpu.value("available", false)) {
            std::string name = igpu.value("name", "");
            std::string family = igpu.value("family", "");
            if (!name.empty()) {
                detected_devices.push_back({
                    "amd_igpu",
                    name,
                    family,
                    true
                });
            }
        }
    }

    // AMD dGPUs
    if (devices.contains("amd_dgpu") && devices["amd_dgpu"].is_array()) {
        for (const auto& gpu : devices["amd_dgpu"]) {
            if (gpu.value("available", false)) {
                std::string name = gpu.value("name", "");
                std::string family = gpu.value("family", "");
                if (!name.empty()) {
                    detected_devices.push_back({
                        "amd_dgpu",
                        name,
                        family,
                        true
                    });
                }
            }
        }
    }

    // AMD NPU
    if (devices.contains("amd_npu") && devices["amd_npu"].is_object()) {
        const auto& npu = devices["amd_npu"];
        if (npu.value("available", false)) {
            std::string name = npu.value("name", "");
            std::string family = npu.value("family", "");
            detected_devices.push_back({
                "amd_npu",
                name,
                family,
                true
            });
        }
    }

    // Metal
    if (devices.contains("metal")) {
        if (devices["metal"].is_object()) {
            // Single Metal device (legacy format)
            const auto& metal = devices["metal"];
            if (metal.value("available", false)) {
                std::string name = metal.value("name", "");
                std::string family = metal.value("family", "");
                detected_devices.push_back({
                    "metal",
                    name,
                    family,
                    true
                });
            }
        } else if (devices["metal"].is_array()) {
            // Multiple Metal devices
            for (const auto& metal : devices["metal"]) {
                if (metal.value("available", false)) {
                    std::string name = metal.value("name", "");
                    std::string family = metal.value("family", "");
                    if (!name.empty()) {
                        detected_devices.push_back({
                            "metal",
                            name,
                            family,
                            true
                        });
                    }
                }
            }
        }
    }

    // Special case: Metal is always available on macOS (system GPU)
    if (current_os == "macos" && std::find_if(detected_devices.begin(), detected_devices.end(),
        [](const DetectedDevice& d) { return d.type == "metal"; }) == detected_devices.end()) {
        detected_devices.push_back({"metal", "Apple Metal", "metal", true});
    }

    // Check if user prefers system llamacpp backend (off by default)
    bool prefer_llamacpp_system = false;
    if (auto* cfg = RuntimeConfig::global()) {
        prefer_llamacpp_system = cfg->backend_bool("llamacpp", "prefer_system");
    }

    // Build recipes from the definition table
    for (const auto& def : RECIPE_DEFS) {
        // Skip if not supported on current OS
        if (def.supported_os.count(current_os) == 0) {
            // Helper to format OS name nicely
            auto format_os_name = [](const std::string& os) -> std::string {
                if (os == "macos") return "macOS";
                if (os == "windows") return "Windows";
                if (os == "linux") return "Linux";
                // Fallback: capitalize first letter
                std::string result = os;
                if (!result.empty()) result[0] = std::toupper(result[0]);
                return result;
            };

            // Generate concise OS requirement message
            std::string required_os;
            if (def.supported_os.size() == 1) {
                required_os = format_os_name(*def.supported_os.begin());
            } else {
                for (const auto& os : def.supported_os) {
                    if (!required_os.empty()) required_os += "/";
                    required_os += format_os_name(os);
                }
            }

            std::string message = "Requires " + required_os;
            // Still add the recipe but mark as unsupported
            json backend = {
                {"devices", json::array()},
                {"state", "unsupported"},
                {"message", message},
                {"action", ""},
                {"can_uninstall", true},
            };

            // Add to the appropriate recipe/backend structure
            if (recipes.contains(def.recipe)) {
                recipes[def.recipe]["backends"][def.backend] = backend;
            } else {
                recipes[def.recipe] = {{"backends", {{def.backend, backend}}}};
            }
            continue;
        }

        // Find matching devices on this system and track failures for error reporting
        json matching_devices = json::array();
        // Track missing devices with their required families for error messages
        std::vector<std::pair<std::string, std::set<std::string>>> missing_devices;  // Device types not present
        std::vector<std::pair<std::string, std::set<std::string>>> wrong_family;     // Device present but wrong family

        for (const auto& [required_device_type, required_families] : def.devices) {
            bool device_type_found = false;
            bool family_matched = false;

            for (const auto& detected : detected_devices) {
                if (detected.type == required_device_type) {
                    device_type_found = true;
                    if (device_matches_constraint(detected.family, required_families)) {
                        matching_devices.push_back(detected.type);
                        family_matched = true;
                    }
                }
            }

            if (!device_type_found) {
                missing_devices.push_back({required_device_type, required_families});
            } else if (!family_matched) {
                wrong_family.push_back({required_device_type, required_families});
            }
        }

        // Remove duplicates (e.g., multiple dGPUs of same type)
        std::set<std::string> unique_devices;
        json unique_matching = json::array();
        for (const auto& dev : matching_devices) {
            if (unique_devices.insert(dev.get<std::string>()).second) {
                unique_matching.push_back(dev);
            }
        }

        bool supported = !unique_matching.empty();
        std::string install_error;
        bool available = is_recipe_installed(def.recipe, def.backend, install_error);

        json backend = {
            {"devices", unique_matching}
        };

        // Special case for 'system' backend: it's either installed or unsupported.
        // It's never 'installable' because Lemonade cannot install system packages.
        if (def.backend == "system") {
            if (available) {
                supported = true; // Ensure it's not marked unsupported due to device logic
            } else {
                supported = false;
                // Only set generic error if a specific error wasn't already set
                if (install_error.empty()) {
                    auto* spec = backends::try_get_spec_for_recipe(def.recipe);
                    install_error = (spec ? spec->binary : "binary") + " not found in PATH";
                }
            }
        }

        if (!supported) {
            std::string message;

            if (def.backend == "system" && !available) {
                message = install_error;
            } else if (!missing_devices.empty() || !wrong_family.empty()) {
                // Get the first unsupported device (prefer wrong family over missing,
                // since wrong family means we detected the device but it's not supported)
                const auto& [device_type, required_families] = !wrong_family.empty()
                    ? wrong_family[0]
                    : missing_devices[0];

                // For AMD GPUs, include the detected family in the message
                if (device_type == "amd_dgpu" || device_type == "amd_igpu") {
                    // Find the detected GPU family for this device type
                    std::string detected_family;
                    for (const auto& detected : detected_devices) {
                        if (detected.type == device_type && !detected.family.empty()) {
                            detected_family = detected.family;
                            break;
                        }
                    }

                    if (!detected_family.empty()) {
                        message = "Unsupported GPU: " + detected_family;
                    } else {
                        message = "Unsupported GPU";
                    }
                } else if (!required_families.empty()) {
                    // Show specific family requirement for other devices (e.g., "Requires XDNA2 NPU")
                    message = "Requires " + get_family_name(*required_families.begin()) + " " + get_device_type_name(device_type);
                } else {
                    // No specific family required (e.g., "Requires CPU")
                    message = "Requires " + get_device_type_name(device_type);
                }
            } else {
                message = "No compatible device";
            }
            backend["state"] = "unsupported";
            backend["message"] = message;
            backend["action"] = "";
        } else if (!available) {
            // FLM on Linux needs richer state to guide users through manual setup
            // (installing .deb, xrt drivers, etc.)
            if (def.recipe == "flm") {
                bool is_not_installed = install_error.empty()
                                     || install_error.find("not installed") != std::string::npos
                                     || install_error.find("not found") != std::string::npos;
                bool is_version_mismatch = install_error.find("requires") != std::string::npos;

                if (is_not_installed) {
                    backend["state"] = "installable";
                } else if (is_version_mismatch) {
                    backend["state"] = "update_required";
                } else {
                    backend["state"] = "action_required";
                }
                backend["message"] = install_error;

                if (!is_not_installed) {
                    std::string installed_version = get_recipe_version(def.recipe, def.backend);
                    if (!installed_version.empty() && installed_version != "unknown") {
                        backend["version"] = installed_version;
                    }
                }

#ifdef __linux__
                backend["action"] = "Visit https://lemonade-server.ai/flm_npu_linux.html?mode=troubleshoot";
#elif defined(_WIN32)
                if (!is_not_installed && !is_version_mismatch) {
                    backend["action"] = "Visit https://lemonade-server.ai/driver_install.html";
                } else {
                    backend["action"] = get_install_command(def.recipe, def.backend);
                }
#else
                backend["action"] = get_install_command(def.recipe, def.backend);
#endif
            } else {
                backend["state"] = "installable";
                backend["message"] = install_error.empty() ? "Backend is supported but not installed." : install_error;

                // Special action for ROCm backend on llamacpp/sd-cpp if CWSR fix is missing
                if ((def.recipe == "llamacpp" || def.recipe == "sd-cpp") && def.backend == "rocm"
                    && !install_error.empty() && needs_gfx1151_cwsr_fix()) {
                    backend["action"] = "Visit https://lemonade-server.ai/gfx1151_linux.html";
                } else {
                    backend["action"] = get_install_command(def.recipe, def.backend);
                }
            }
        } else {
            std::string installed_version = get_recipe_version(def.recipe, def.backend);
            std::string expected_version = get_expected_backend_version(def.recipe, def.backend);

            if (!installed_version.empty() && installed_version != "unknown") {
                backend["version"] = installed_version;
            }

            bool version_known = !installed_version.empty() && installed_version != "unknown";
            bool has_expected = !expected_version.empty();
            bool needs_update = has_expected && (!version_known || installed_version != expected_version);

            if (needs_update) {
                backend["state"] = "update_required";
                backend["message"] = "Backend update is required before use.";
                backend["action"] = get_install_command(def.recipe, def.backend);
            } else {
                backend["state"] = "installed";
                backend["message"] = "";
                backend["action"] = "";
            }
        }

        // Note: release_url and download_size_mb are added by Server::handle_system_info()
        // using BackendManager as the single source of truth for repo/version mappings.

        // Add to the appropriate recipe/backend structure
        if (!recipes.contains(def.recipe)) {
            recipes[def.recipe] = {{"backends", json::object()}};
        }
        recipes[def.recipe]["backends"][def.backend] = backend;

        // First supported backend in RECIPE_DEFS order becomes the default.
        // Skip 'system' backend unless explicitly preferred via env var.
        bool skip_as_default = (def.backend == "system" && !prefer_llamacpp_system);
        if (supported && !skip_as_default && !recipes[def.recipe].contains("default_backend")) {
            recipes[def.recipe]["default_backend"] = def.backend;
        }
    }

    return recipes;
}

SystemInfo::SupportedBackendsResult SystemInfo::get_supported_backends(const std::string& recipe) {
    SupportedBackendsResult result;
    json system_info = SystemInfoCache::get_system_info_with_cache();

    if (!system_info.contains("recipes") || !system_info["recipes"].contains(recipe)) {
        result.not_supported_error = "Recipe '" + recipe + "' not found";
        return result;
    }

    const auto& recipe_info = system_info["recipes"][recipe];
    if (!recipe_info.contains("backends")) {
        result.not_supported_error = "No backends found for recipe '" + recipe + "'";
        return result;
    }

    // If a default_backend is specified, add it first (if supported)
    std::string default_backend;
    if (recipe_info.contains("default_backend")) {
        default_backend = recipe_info["default_backend"].get<std::string>();
        if (recipe_info["backends"].contains(default_backend)) {
            const auto& backend = recipe_info["backends"][default_backend];
            std::string state = backend.value("state", "unsupported");
            if (state != "unsupported") {
                result.backends.push_back(default_backend);
            }
        }
    }

    // Collect remaining supported backends and capture first error (in preference order from RECIPE_DEFS)
    for (const auto& def : RECIPE_DEFS) {
        if (def.recipe == recipe) {
            // Skip the default_backend since we already added it
            if (def.backend == default_backend) {
                continue;
            }

            if (recipe_info["backends"].contains(def.backend)) {
                const auto& backend = recipe_info["backends"][def.backend];
                std::string state = backend.value("state", "unsupported");
                if (state != "unsupported") {
                    result.backends.push_back(def.backend);
                } else if (result.not_supported_error.empty() && backend.contains("message")) {
                    // Capture first error encountered (in preference order)
                    result.not_supported_error = backend["message"].get<std::string>();
                }
            }
        }
    }

    // If no backends supported and no specific error, provide generic message
    if (result.backends.empty() && result.not_supported_error.empty()) {
        result.not_supported_error = "No supported backend found for recipe '" + recipe + "'";
    }

    return result;
}

std::string SystemInfo::check_recipe_supported(const std::string& recipe) {
    auto result = get_supported_backends(recipe);
    return result.backends.empty() ? result.not_supported_error : "";
}

std::vector<SystemInfo::RecipeStatus> SystemInfo::get_all_recipe_statuses() {
    std::vector<RecipeStatus> statuses;
    json system_info = SystemInfoCache::get_system_info_with_cache();

    if (!system_info.contains("recipes") || !system_info["recipes"].is_object()) {
        return statuses;
    }

    const auto& recipes = system_info["recipes"];
    for (auto& [recipe_name, recipe_info] : recipes.items()) {
        std::vector<BackendStatus> backends;

        if (recipe_info.contains("backends") && recipe_info["backends"].is_object()) {
            // Iterate in preference order (from RECIPE_DEFS table)
            for (const auto& def : RECIPE_DEFS) {
                if (def.recipe != recipe_name) continue;

                if (!recipe_info["backends"].contains(def.backend)) continue;

                const auto& backend_info = recipe_info["backends"][def.backend];
                std::string state = backend_info.value("state", "unsupported");
                std::string version = backend_info.value("version", "");
                std::string message = backend_info.value("message", "");
                std::string action = backend_info.value("action", "");
                backends.push_back({def.backend, state, version, message, action});
            }
        }

        statuses.push_back({recipe_name, backends});
    }

    return statuses;
}

// Helper to read version from a version.txt file
static std::string read_version_file(const fs::path& version_file) {
    if (fs::exists(version_file)) {
        std::ifstream file(version_file);
        if (file.is_open()) {
            std::string version;
            std::getline(file, version);
            file.close();
            // Trim whitespace
            size_t start = version.find_first_not_of(" \t\n\r");
            size_t end = version.find_last_not_of(" \t\n\r");
            if (start != std::string::npos && end != std::string::npos) {
                return version.substr(start, end - start + 1);
            }
        }
    }
    return "unknown";
}

std::string SystemInfo::get_system_llamacpp_version() {
    std::string output;
    #ifdef _WIN32
    std::string command = "llama-server --version 2>NUL";
    int rc = lemon::utils::ProcessManager::run_command(command, output);
    #else
    FILE* pipe = popen("llama-server --version 2>/dev/null", "r");
    if (!pipe) {
        return "unknown";
    }

    char buffer[256];
    if (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
        output = buffer;
    }

    pclose(pipe);
    #endif

    // Parse version from output like "version: 3432 (e2b2a632)" or "llama.cpp version b3432"
    if (!output.empty()) {
        // Try to find a version number
        std::regex version_regex(R"(version:\s*(\d+)|version\s+b?(\d+))");
        std::smatch match;
        if (std::regex_search(output, match, version_regex)) {
            for (size_t i = 1; i < match.size(); ++i) {
                if (match[i].matched) {
                    return "b" + match[i].str();
                }
            }
        }
        return "detected";
    }

    return "unknown";
}

// Helper to identify ROCm architecture from GPU name
// Returns the detected architecture even if not in ROCM_ARCH_MAPPING
std::string identify_rocm_arch_from_name(const std::string& device_name) {
    std::string device_lower = device_name;
    std::transform(device_lower.begin(), device_lower.end(), device_lower.begin(), ::tolower);

    // Linux will pass the ISA from KFD, transform it to what the rest of lemonade expects
    if (std::all_of(device_lower.begin(), device_lower.end(), ::isdigit)) {
        if (device_lower.length() >= 4) {
            std::string major = device_lower.substr(0, 2);

            int minor_int = std::stoi(device_lower.substr(2, 2));
            std::string minor = std::to_string(minor_int);

            int revision_int = std::stoi(device_lower.substr(4, 2));
            std::string revision = std::to_string(revision_int);

            std::string arch = "gfx" + major + minor + revision;

            // Apply architecture family mapping if available
            // Otherwise return the detected arch for unsupported GPUs
            auto it = ROCM_ARCH_MAPPING.find(arch);
            if (it != ROCM_ARCH_MAPPING.end()) {
                return it->second;
            }

            return arch;  // Return the detected arch even if unsupported
        }
    }

    if (device_lower.find("radeon") == std::string::npos &&
        device_lower.find("amd") == std::string::npos) {
        return "";
    }

    // STX Halo iGPUs (gfx1151 architecture)
    // Radeon 8050S Graphics / Radeon 8060S Graphics
    if (device_lower.find("8050s") != std::string::npos ||
        device_lower.find("8060s") != std::string::npos ||
        device_lower.find("device 1586") != std::string::npos) {
        return "gfx1151";
    }

    // STX Point iGPUs (gfx1150 architecture)
    // Radeon 880M / 890M Graphics
    if (device_lower.find("880m") != std::string::npos ||
        device_lower.find("890m") != std::string::npos) {
        return "gfx1150";
    }

    // RDNA4 GPUs (gfx120X architecture)
    // AMD Radeon AI PRO R9700, AMD Radeon RX 9070 XT, AMD Radeon RX 9070 GRE,
    // AMD Radeon RX 9070, AMD Radeon RX 9060 XT
    if (device_lower.find("r9700") != std::string::npos ||
        device_lower.find("9060") != std::string::npos ||
        device_lower.find("9070") != std::string::npos) {
        return "gfx120X";
    }

    // RDNA3 GPUs (gfx110X architecture)
    // AMD Radeon PRO V710, AMD Radeon PRO W7900 Dual Slot, AMD Radeon PRO W7900,
    // AMD Radeon PRO W7800 48GB, AMD Radeon PRO W7800, AMD Radeon PRO W7700,
    // AMD Radeon RX 7900 XTX, AMD Radeon RX 7900 XT, AMD Radeon RX 7900 GRE,
    // AMD Radeon RX 7800 XT, AMD Radeon RX 7700 XT
    if (device_lower.find("7700") != std::string::npos ||
        device_lower.find("7800") != std::string::npos ||
        device_lower.find("7900") != std::string::npos ||
        device_lower.find("v710") != std::string::npos) {
        return "gfx110X";
    }

    // RDNA2 GPUs (gfx103X architecture)
    // AMD Radeon RX 6800 XT, AMD Radeon RX 6800, AMD Radeon RX 6700 XT,
    // AMD Radeon RX 6700, AMD Radeon RX 6600 XT, AMD Radeon RX 6600,
    // AMD Radeon RX 6500 XT, AMD Radeon RX 6500
    if (device_lower.find("6800") != std::string::npos ||
        device_lower.find("6700") != std::string::npos ||
        device_lower.find("6600") != std::string::npos ||
        device_lower.find("6500") != std::string::npos) {
        return "gfx103X";
    }

    return "";
}

// Linux: identify NPU architecture from sysfs accel subsystem
// Checks /sys/class/accel/*/device/driver for amdxdna, then reads number of columns
// If amdxdna not loaded, fall back to PCI device IDs
static std::string identify_npu_arch_linux() {
#ifdef __linux__
    fs::path accel_path = "/sys/class/accel";
    if (!fs::exists(accel_path) || !fs::is_directory(accel_path)) {
        return "";
    }

    for (const auto& entry : fs::directory_iterator(accel_path)) {
        if (!entry.is_directory() && !entry.is_symlink()) {
            continue;
        }

        fs::path driver_link = entry.path() / "device" / "driver";
        if (fs::exists(driver_link)) {
            fs::path driver_path = fs::read_symlink(driver_link);
            std::string driver_name = driver_path.filename().string();

            if (driver_name != "amdxdna") {
                continue;
            }
        } else {
            continue;
        }

        std::string accel_basename = entry.path().filename().string();
        std::string accel_dev = "/dev/accel/" + accel_basename;
        if (!fs::exists(accel_dev))
            continue;

        if (accel_dev.empty() || !fs::exists(accel_dev)) {
            continue;
        }

        int fd = open(accel_dev.c_str(), O_RDWR);
        if (fd < 0)
            continue;
        amdxdna_drm_query_aie_metadata query_aie_metadata = {};
        amdxdna_drm_get_info get_info = {
            .param = DRM_AMDXDNA_QUERY_AIE_METADATA,
            .buffer_size = sizeof(amdxdna_drm_query_aie_metadata),
            .buffer = (unsigned long)&query_aie_metadata,
        };
        ioctl(fd, DRM_IOCTL_AMDXDNA_GET_INFO, &get_info);
        close(fd);
        if (query_aie_metadata.cols == 8) {
            return "XDNA2";
        }

        //Fallback path for missing amdxdna driver (just check PCI IDs)
        fs::path pci_path = "/sys/bus/pci/devices";
        if (fs::exists(pci_path) && fs::is_directory(pci_path)) {
            for (const auto& pci_entry : fs::directory_iterator(pci_path)) {
                if (!pci_entry.is_directory()) continue;

                fs::path class_path = pci_entry.path() / "class";
                fs::path vendor_path = pci_entry.path() / "vendor";
                fs::path device_path = pci_entry.path() / "device";
                fs::path revision_path = pci_entry.path() / "revision";

                if (!fs::exists(class_path) || !fs::exists(vendor_path) || !fs::exists(device_path)) {
                    continue;
                }
                if (!fs::exists(revision_path)) {
                    continue;
                }

                auto read_sysfs = [](const fs::path& p) {
                    std::ifstream is(p);
                    std::string s;
                    is >> s;
                    return s;
                };
                std::string class_str = read_sysfs(class_path);
                std::string vendor_str = read_sysfs(vendor_path);
                std::string device_str = read_sysfs(device_path);
                std::string revision_str = read_sysfs(revision_path);

                if (class_str != "0x118000" || vendor_str != "0x1022") {
                    continue;
                }
                if (device_str == "0x17f0") {
                    if (revision_str == "0x10" ||
                        revision_str == "0x11" ||
                        revision_str == "0x20")
                        return "XDNA2";
                }
            }
        }
    }
#endif
    return "";
}

// Check if kernel has CWSR fix for Strix Halo by looking for cwsr_size/ctl_stack_size in sysfs
// The kernel fix exports these properties; older kernels don't have them
bool needs_gfx1151_cwsr_fix() {
    std::string kfd_path = "/sys/class/kfd/kfd/topology/nodes";

    if (!fs::exists(kfd_path))
        return false;

    for (const auto& node_entry : fs::directory_iterator(kfd_path)) {
        if (!node_entry.is_directory()) continue;

        std::string properties_file = node_entry.path().string() + "/properties";
        if (!fs::exists(properties_file)) continue;

        std::ifstream props(properties_file);
        if (!props.is_open())
            continue;

        bool is_gfx1151 = false;
        bool has_cwsr_size = false;
        bool has_ctl_stack_size = false;

        std::string line;
        while (std::getline(props, line)) {
            if (line.find("gfx_target_version") == 0) {
                std::string value = line.substr(line.find(" ") + 1);
                value.erase(value.find_last_not_of(" \t\n\r") + 1);
                if (value == "110501") {
                    is_gfx1151 = true;
                }
            }

            if (line.find("cwsr_size") == 0)
                has_cwsr_size = true;
            if (line.find("ctl_stack_size") == 0)
                has_ctl_stack_size = true;
        }

        if (is_gfx1151) {
            return !has_cwsr_size || !has_ctl_stack_size;
        }
    }

    return false;
}

// Identify NPU architecture by checking for known hardware
// Windows: PCI device ID via WMI; Linux: amdxdna driver in sysfs accel subsystem
// Returns the NPU family (e.g., "XDNA2") or empty string if no NPU found
// This is the single source of truth for NPU family detection
std::string identify_npu_arch() {
#ifdef _WIN32
    wmi::WMIConnection wmi_conn;
    if (!wmi_conn.is_valid()) {
        return "";
    }

    // XDNA2 NPU: AMD vendor 1022, device 17F0
    bool found_xdna2 = false;
    wmi_conn.query(
        L"SELECT PNPDeviceID FROM Win32_PnPEntity WHERE PNPDeviceID LIKE '%VEN_1022&DEV_17F0%'",
        [&found_xdna2](IWbemClassObject* pObj) {
            found_xdna2 = true;
        });

    if (found_xdna2) {
        return "XDNA2";
    }
#else
    std::string linux_arch = identify_npu_arch_linux();
    if (!linux_arch.empty()) {
        return linux_arch;
    }
#endif

    // Future: Add XDNA3, XDNA4, etc. with their PCI device IDs

    return "";
}

std::string SystemInfo::get_rocm_arch() {
    // Returns the ROCm architecture for the best available AMD GPU on this system
    // Checks iGPU first, then dGPUs. Returns empty string if no compatible GPU found.
    try {
        // Use cached system info to avoid re-detecting GPUs
        json system_info = SystemInfoCache::get_system_info_with_cache();

        if (!system_info.contains("devices")) {
            return "";
        }

        const auto& devices = system_info["devices"];

        // Check iGPU first
        if (devices.contains("amd_igpu") && devices["amd_igpu"].is_object()) {
            const auto& igpu = devices["amd_igpu"];
            if (igpu.value("available", false)) {
                std::string name = igpu.value("name", "");
                if (!name.empty()) {
                    std::string arch = identify_rocm_arch_from_name(name);
                    if (!arch.empty()) {
                        return arch;
                    }
                }
            }
        }

        // Check dGPUs
        if (devices.contains("amd_dgpu") && devices["amd_dgpu"].is_array()) {
            for (const auto& gpu : devices["amd_dgpu"]) {
                if (gpu.value("available", false)) {
                    std::string name = gpu.value("name", "");
                    if (!name.empty()) {
                        std::string arch = identify_rocm_arch_from_name(name);
                        if (!arch.empty()) {
                            return arch;
                        }
                    }
                }
            }
        }
    } catch (...) {
        // Detection failed
    }

    return "";  // No supported architecture found
}

bool SystemInfo::get_has_igpu() {
    // Returns if the device is an iGPU. Only AMD iGPUs are detected at the moment
    try {
        // Use cached system info to avoid re-detecting GPUs
        json system_info = SystemInfoCache::get_system_info_with_cache();

        if (!system_info.contains("devices")) {
            return false;
        }

        const auto& devices = system_info["devices"];

        // Check iGPU first
        if (devices.contains("amd_igpu") && devices["amd_igpu"].is_object()) {
            const auto& igpu = devices["amd_igpu"];
            if (igpu.value("available", false)) {
                return true;
            }
        }
    } catch (...) {
        // Detection failed
    }

    return false;  // No iGPU detected
}

std::string SystemInfo::get_flm_version() {
    // Cache real version strings to avoid spawning the subprocess twice per
    // build_recipes_info() pass. "unknown" is NOT cached so that post-install
    // verification in fastflowlm_server.cpp gets a fresh result after FLM is installed.
    static std::string cached_version;
    if (!cached_version.empty()) {
        return cached_version;
    }

    // Find the flm executable using shared utility
    std::string flm_path = utils::find_flm_executable();
    if (flm_path.empty() || !utils::is_safe_executable_path(flm_path)) {
        return "unknown";
    }

    std::string output;
    #ifdef _WIN32
    std::string command = "\"" + flm_path + "\" version --json 2>NUL";
    int rc = lemon::utils::ProcessManager::run_command(command, output);
    #else
    std::string command = "\"" + flm_path + "\" version --json 2>/dev/null";
    FILE* pipe = popen(command.c_str(), "r");
    if (!pipe) {
        return "unknown";
    }

    char buffer[256];
    while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
        output += buffer;
    }

    pclose(pipe);
    #endif

    // Parse JSON output: { "version": "0.9.34" }
    try {
        json j = JsonUtils::parse(output);
        if (j.contains("version") && j["version"].is_string()) {
            std::string version = j["version"].get<std::string>();
            // If the version doesn't start with 'v', prepend it
            // for backend_versions.json compatibility (e.g. "v0.9.34").
            if (!version.empty() && version[0] != 'v') {
                version = "v" + version;
            }
            cached_version = version;
            return cached_version;
        }
    } catch (...) {
        // Fallback to legacy parsing if JSON parsing fails
    }

    // Legacy parsing from output like "FLM v0.9.4"
    if (output.find("FLM v") != std::string::npos) {
        size_t pos = output.find("FLM v");
        // Keep the 'v' prefix so it matches backend_versions.json (e.g. "v0.9.34").
        std::string version = output.substr(pos + 4);
        // Trim whitespace and newlines
        size_t end = version.find_first_of(" \t\n\r");
        if (end != std::string::npos) {
            version = version.substr(0, end);
        }
        cached_version = version;
        return cached_version;
    }

    return "unknown";
}

// ============================================================================
// Factory function
// ============================================================================

std::unique_ptr<SystemInfo> create_system_info() {
    #ifdef _WIN32
    return std::make_unique<WindowsSystemInfo>();
    #elif __linux__
    return std::make_unique<LinuxSystemInfo>();
    #elif __APPLE__
    return std::make_unique<MacOSSystemInfo>();
    #else
    throw std::runtime_error("Unsupported operating system");
    #endif
}

// ============================================================================
// Windows implementation
// ============================================================================

#ifdef _WIN32

WindowsSystemInfo::WindowsSystemInfo() {
    // COM initialization handled by WMIConnection
}

// Read CPU brand string, physical core count, and logical processor count in one pass.
static WindowsSystemInfo::CpuHardware read_cpu_hardware() {
    WindowsSystemInfo::CpuHardware hw;

    // Brand string via CPUID leaves 0x80000002-4
    char brand[49] = {};
    int cpui[4] = {};
    for (int leaf = 0x80000002; leaf <= 0x80000004; ++leaf) {
        __cpuid(cpui, leaf);
        memcpy(brand + (leaf - 0x80000002) * 16, cpui, 16);
    }
    brand[48] = '\0';
    hw.brand = brand;
    size_t start = hw.brand.find_first_not_of(" \t");
    size_t end   = hw.brand.find_last_not_of(" \t");
    if (start != std::string::npos) {
        hw.brand = hw.brand.substr(start, end - start + 1);
    }

    // Logical processor count
    SYSTEM_INFO si = {};
    GetSystemInfo(&si);
    hw.logical = static_cast<int>(si.dwNumberOfProcessors);

    // Physical core count
    DWORD buf_size = 0;
    GetLogicalProcessorInformation(NULL, &buf_size);
    std::vector<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> lpi_buf(
        buf_size / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION));
    if (GetLogicalProcessorInformation(lpi_buf.data(), &buf_size)) {
        for (const auto& entry : lpi_buf) {
            if (entry.Relationship == RelationProcessorCore) {
                ++hw.physical;
            }
        }
    }

    return hw;
}

CPUInfo WindowsSystemInfo::get_cpu_device() {
    CPUInfo cpu;
    auto hw = read_cpu_hardware();
    cpu.name    = hw.brand;
    cpu.threads = hw.logical;
    cpu.cores   = hw.physical;
    cpu.available = !cpu.name.empty();
    if (!cpu.available) {
        cpu.error = "No CPU information found";
    }
    return cpu;
}

GPUInfo WindowsSystemInfo::get_amd_igpu_device() {
    auto gpus = detect_amd_gpus("integrated");
    if (!gpus.empty()) {
        return gpus[0];
    }

    GPUInfo gpu;
    gpu.available = false;
    gpu.error = "No AMD integrated GPU found";
    return gpu;
}

std::vector<GPUInfo> WindowsSystemInfo::get_amd_dgpu_devices() {
    return detect_amd_gpus("discrete");
}

std::vector<GPUInfo> WindowsSystemInfo::get_nvidia_dgpu_devices() {
    std::vector<GPUInfo> gpus;

    wmi::WMIConnection wmi;
    if (!wmi.is_valid()) {
        GPUInfo gpu;
        gpu.available = false;
        gpu.error = "Failed to connect to WMI";
        gpus.push_back(gpu);
        return gpus;
    }

    wmi.query(L"SELECT * FROM Win32_VideoController", [&gpus, this](IWbemClassObject* pObj) {
        std::string name = wmi::get_property_string(pObj, L"Name");

        // Check if this is an NVIDIA GPU
        if (name.find("NVIDIA") != std::string::npos) {
            std::string name_lower = name;
            std::transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower);

            // Most NVIDIA GPUs are discrete
            bool is_discrete = true;
            for (const auto& keyword : NVIDIA_DISCRETE_GPU_KEYWORDS) {
                if (name_lower.find(keyword) != std::string::npos) {
                    is_discrete = true;
                    break;
                }
            }

            if (is_discrete) {
                GPUInfo gpu;
                gpu.name = name;
                gpu.available = true;

                // Get driver version - try multiple methods
                std::string driver_version = get_driver_version("NVIDIA");
                if (driver_version.empty()) {
                    driver_version = wmi::get_property_string(pObj, L"DriverVersion");
                }
                gpu.driver_version = driver_version.empty() ? "Unknown" : driver_version;

                // Get VRAM
                uint64_t adapter_ram = wmi::get_property_uint64(pObj, L"AdapterRAM");
                if (adapter_ram > 0) {
                    gpu.vram_gb = adapter_ram / (1024.0 * 1024.0 * 1024.0);
                }

                gpus.push_back(gpu);
            }
        }
    });

    if (gpus.empty()) {
        GPUInfo gpu;
        gpu.available = false;
        gpu.error = "No NVIDIA discrete GPU found";
        gpus.push_back(gpu);
    }

    return gpus;
}

NPUInfo WindowsSystemInfo::get_npu_device() {
    NPUInfo npu;
    npu.name = "AMD NPU";
    npu.available = false;

    // Check for XDNA NPU hardware via PCI device ID
    std::string npu_arch = identify_npu_arch();
    if (npu_arch.empty()) {
        npu.error = "No XDNA NPU hardware detected";
        return npu;
    }

    // Check for NPU driver
    std::string driver_version = get_driver_version("NPU Compute Accelerator Device");
    if (!driver_version.empty()) {
        npu.available = true;
    } else {
        npu.error = "NPU hardware found but driver not installed";
    }

    return npu;
}

std::vector<GPUInfo> WindowsSystemInfo::detect_amd_gpus(const std::string& gpu_type) {
    std::vector<GPUInfo> gpus;

    wmi::WMIConnection wmi;
    if (!wmi.is_valid()) {
        GPUInfo gpu;
        gpu.available = false;
        gpu.error = "Failed to connect to WMI";
        gpus.push_back(gpu);
        return gpus;
    }

    wmi.query(L"SELECT * FROM Win32_VideoController", [&gpus, &gpu_type, this](IWbemClassObject* pObj) {
        std::string name = wmi::get_property_string(pObj, L"Name");

        // Check if this is an AMD Radeon GPU
        if (name.find("AMD") != std::string::npos && name.find("Radeon") != std::string::npos) {
            // Convert to lowercase for keyword matching
            std::string name_lower = name;
            std::transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower);

            // Classify as discrete or integrated based on keywords
            bool is_discrete = false;
            for (const auto& keyword : AMD_DISCRETE_GPU_KEYWORDS) {
                if (name_lower.find(keyword) != std::string::npos) {
                    is_discrete = true;
                    break;
                }
            }
            bool is_integrated = !is_discrete;

            // Filter based on requested type
            if ((gpu_type == "integrated" && is_integrated) ||
                (gpu_type == "discrete" && is_discrete)) {

                GPUInfo gpu;
                gpu.name = name;
                gpu.available = true;

                // Get driver version
                gpu.driver_version = get_driver_version("AMD-OpenCL User Mode Driver");
                if (gpu.driver_version.empty()) {
                    gpu.driver_version = "Unknown";
                }

                // Get VRAM for discrete GPUs
                if (is_discrete) {
                    // Try dxdiag first (most reliable for dedicated memory)
                    double vram_gb = get_gpu_vram_dxdiag(name);

                    // Fallback to WMI if dxdiag fails
                    if (vram_gb == 0.0) {
                        uint64_t adapter_ram = wmi::get_property_uint64(pObj, L"AdapterRAM");
                        vram_gb = get_gpu_vram_wmi(adapter_ram);
                    }

                    if (vram_gb > 0.0) {
                        gpu.vram_gb = vram_gb;
                    }
                }

                gpus.push_back(gpu);
            }
        }
    });

    if (gpus.empty()) {
        GPUInfo gpu;
        gpu.available = false;
        gpu.error = "No AMD " + gpu_type + " GPU found";
        gpus.push_back(gpu);
    }

    return gpus;
}

std::string WindowsSystemInfo::get_driver_version(const std::string& device_name) {
    return wmi::get_driver_version_setupapi(device_name);
}


json WindowsSystemInfo::get_system_info_dict() {
    json info = SystemInfo::get_system_info_dict();  // Get base fields (includes OS Version)
    info["Processor"] = get_processor_name();
    info["Physical Memory"] = get_physical_memory();
    return info;
}

std::string WindowsSystemInfo::get_os_version() {
    // Read Windows version from registry — much faster than WMI Win32_OperatingSystem
    HKEY hkey = NULL;
    if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                      L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
                      0, KEY_READ, &hkey) != ERROR_SUCCESS) {
        return "Windows";
    }

    auto read_reg_str = [&](const wchar_t* name) -> std::string {
        wchar_t buf[256] = {};
        DWORD size = sizeof(buf);
        if (RegQueryValueExW(hkey, name, NULL, NULL, (LPBYTE)buf, &size) == ERROR_SUCCESS) {
            return wmi::wstring_to_string(buf);
        }
        return "";
    };

    // ProductName is stale on Windows 11 (reports "Windows 10"); detect via build number
    std::string product = read_reg_str(L"ProductName");
    std::string version = read_reg_str(L"CurrentVersion");    // e.g. "10.0"
    std::string build   = read_reg_str(L"CurrentBuildNumber");
    RegCloseKey(hkey);

    if (product.empty()) {
        return "Windows";
    }

    // Reconstruct a clean name: on Windows 11 the build number is >= 22000
    // and ProductName incorrectly says "Windows 10"
    std::string result = product;
    try {
        if (!build.empty() && std::stoi(build) >= 22000) {
            // Replace "Windows 10" with "Windows 11" in the product name
            std::string w10 = "Windows 10";
            auto pos = result.find(w10);
            if (pos != std::string::npos) {
                result.replace(pos, w10.size(), "Windows 11");
            }
        }
    } catch (...) {}

    if (!version.empty()) result += " " + version;
    if (!build.empty())   result += " (Build " + build + ")";
    return result;
}

std::string WindowsSystemInfo::get_processor_name() {
    auto hw = read_cpu_hardware();
    if (hw.brand.empty()) {
        return "Processor information not found.";
    }
    if (hw.physical > 0) {
        return hw.brand + " (" + std::to_string(hw.physical) + " cores, " +
               std::to_string(hw.logical) + " logical processors)";
    }
    return hw.brand;
}

std::string WindowsSystemInfo::get_physical_memory() {
    // Use WMI Win32_PhysicalMemory to get actual installed DIMM capacity.
    // GlobalMemoryStatusEx reports usable RAM (excludes hardware-reserved memory)
    // which can underreport by 20-30% on unified-memory systems, making it
    // unsuitable for model size calculations.
    wmi::WMIConnection wmi;
    if (!wmi.is_valid()) {
        return "Physical memory information not found.";
    }

    uint64_t total_capacity = 0;
    wmi.query(L"SELECT Capacity FROM Win32_PhysicalMemory", [&](IWbemClassObject* pObj) {
        total_capacity += wmi::get_property_uint64(pObj, L"Capacity");
    });

    if (total_capacity > 0) {
        double gb = total_capacity / (1024.0 * 1024.0 * 1024.0);
        std::ostringstream oss;
        oss << std::fixed << std::setprecision(2) << gb << " GB";
        return oss.str();
    }

    return "Physical memory information not found.";
}


double WindowsSystemInfo::get_gpu_vram_wmi(uint64_t adapter_ram) {
    if (adapter_ram > 0) {
        return adapter_ram / (1024.0 * 1024.0 * 1024.0);
    }
    return 0.0;
}

double WindowsSystemInfo::get_gpu_vram_dxdiag(const std::string& gpu_name) {
    // Get GPU VRAM using dxdiag (most reliable for dedicated memory)
    // Similar to Python's _get_gpu_vram_dxdiag_simple method

    try {
        // Create temp file path
        char temp_path[MAX_PATH];
        char temp_dir[MAX_PATH];
        GetTempPathA(MAX_PATH, temp_dir);
        GetTempFileNameA(temp_dir, "dxd", 0, temp_path);

        // Run dxdiag /t temp_path (use CreateProcess to avoid console window flash)
        std::string command = "dxdiag /t \"" + std::string(temp_path) + "\"";
        STARTUPINFOA si = {};
        si.cb = sizeof(si);
        PROCESS_INFORMATION pi = {};
        if (!CreateProcessA(nullptr, const_cast<char*>(command.c_str()),
                           nullptr, nullptr, FALSE, CREATE_NO_WINDOW,
                           nullptr, nullptr, &si, &pi)) {
            DeleteFileA(temp_path);
            return 0.0;
        }
        // Wait for dxdiag to finish (up to 10s)
        WaitForSingleObject(pi.hProcess, 10000);
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);

        // Read the file
        std::ifstream file(temp_path);
        if (!file.is_open()) {
            DeleteFileA(temp_path);
            return 0.0;
        }

        std::string line;
        bool found_gpu = false;
        double vram_gb = 0.0;

        // Convert gpu_name to lowercase for case-insensitive comparison
        std::string gpu_name_lower = gpu_name;
        std::transform(gpu_name_lower.begin(), gpu_name_lower.end(), gpu_name_lower.begin(), ::tolower);

        while (std::getline(file, line)) {
            // Convert line to lowercase for case-insensitive search
            std::string line_lower = line;
            std::transform(line_lower.begin(), line_lower.end(), line_lower.begin(), ::tolower);

            // Check if this is our GPU
            if (line_lower.find("card name:") != std::string::npos &&
                line_lower.find(gpu_name_lower) != std::string::npos) {
                found_gpu = true;
                continue;
            }

            // Look for dedicated memory line
            if (found_gpu && line_lower.find("dedicated memory:") != std::string::npos) {
                // Extract memory value (format: "Dedicated Memory: 12345 MB")
                std::regex memory_regex(R"((\d+(?:\.\d+)?)\s*MB)", std::regex::icase);
                std::smatch match;
                if (std::regex_search(line, match, memory_regex)) {
                    try {
                        double vram_mb = std::stod(match[1].str());
                        vram_gb = std::round(vram_mb / 1024.0 * 10.0) / 10.0;  // Convert to GB, round to 1 decimal
                        break;
                    } catch (...) {
                        // Continue searching
                    }
                }
            }

            // Reset if we hit another display device
            if (line_lower.find("card name:") != std::string::npos &&
                line_lower.find(gpu_name_lower) == std::string::npos) {
                found_gpu = false;
            }
        }

        file.close();
        DeleteFileA(temp_path);

        return vram_gb;
    } catch (...) {
        return 0.0;
    }
}

#endif // _WIN32

// ============================================================================
// Linux implementation
// ============================================================================

#ifdef __linux__

CPUInfo LinuxSystemInfo::get_cpu_device() {
    CPUInfo cpu;
    cpu.available = false;

    // Execute lscpu command
    FILE* pipe = popen("lscpu 2>/dev/null", "r");
    if (!pipe) {
        cpu.error = "Failed to execute lscpu command";
        return cpu;
    }

    char buffer[256];
    std::string lscpu_output;
    while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
        lscpu_output += buffer;
    }
    pclose(pipe);

    // Parse lscpu output
    std::istringstream iss(lscpu_output);
    std::string line;
    int cores_per_socket = 0;
    int sockets = 1;  // Default to 1

    while (std::getline(iss, line)) {
        if (line.find("Model name:") != std::string::npos) {
            size_t pos = line.find(":");
            if (pos != std::string::npos) {
                cpu.name = line.substr(pos + 1);
                // Trim whitespace
                size_t start = cpu.name.find_first_not_of(" \t");
                size_t end = cpu.name.find_last_not_of(" \t");
                if (start != std::string::npos && end != std::string::npos) {
                    cpu.name = cpu.name.substr(start, end - start + 1);
                }
                cpu.available = true;
            }
        } else if (line.find("CPU(s):") != std::string::npos && line.find("NUMA") == std::string::npos) {
            size_t pos = line.find(":");
            if (pos != std::string::npos) {
                std::string threads_str = line.substr(pos + 1);
                cpu.threads = std::stoi(threads_str);
            }
        } else if (line.find("Core(s) per socket:") != std::string::npos) {
            size_t pos = line.find(":");
            if (pos != std::string::npos) {
                std::string cores_str = line.substr(pos + 1);
                cores_per_socket = std::stoi(cores_str);
            }
        } else if (line.find("Socket(s):") != std::string::npos) {
            size_t pos = line.find(":");
            if (pos != std::string::npos) {
                std::string sockets_str = line.substr(pos + 1);
                sockets = std::stoi(sockets_str);
            }
        }
    }

    // Calculate total cores
    if (cores_per_socket > 0) {
        cpu.cores = cores_per_socket * sockets;
    }

    if (!cpu.available) {
        cpu.error = "No CPU information found";
        return cpu;
    }

    return cpu;
}

GPUInfo LinuxSystemInfo::get_amd_igpu_device() {
    auto gpus = detect_amd_gpus("integrated");
    if (!gpus.empty() && gpus[0].available) {
        return gpus[0];
    }

    GPUInfo gpu;
    gpu.available = false;
    gpu.error = "No AMD integrated GPU found";
    return gpu;
}

std::vector<GPUInfo> LinuxSystemInfo::get_amd_dgpu_devices() {
    return detect_amd_gpus("discrete");
}

std::vector<GPUInfo> LinuxSystemInfo::get_nvidia_dgpu_devices() {
    std::vector<GPUInfo> gpus;

    // Execute lspci to find GPUs
    FILE* pipe = popen("lspci 2>/dev/null | grep -iE 'vga|3d|display'", "r");
    if (!pipe) {
        GPUInfo gpu;
        gpu.available = false;
        gpu.error = "Failed to execute lspci command";
        gpus.push_back(gpu);
        return gpus;
    }

    char buffer[512];
    std::vector<std::string> lspci_lines;
    while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
        lspci_lines.push_back(buffer);
    }
    pclose(pipe);

    // Parse NVIDIA GPUs
    for (const auto& line : lspci_lines) {
        if (line.find("NVIDIA") != std::string::npos || line.find("nvidia") != std::string::npos) {
            // Extract device name
            std::string name;
            size_t pos = line.find(": ");
            if (pos != std::string::npos) {
                name = line.substr(pos + 2);
                // Remove newline
                if (!name.empty() && name.back() == '\n') {
                    name.pop_back();
                }
            } else {
                name = line;
            }

            // Check if discrete (most NVIDIA GPUs are discrete)
            std::string name_lower = name;
            std::transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower);

            bool is_discrete = true;  // Default to discrete for NVIDIA
            for (const auto& keyword : NVIDIA_DISCRETE_GPU_KEYWORDS) {
                if (name_lower.find(keyword) != std::string::npos) {
                    is_discrete = true;
                    break;
                }
            }

            if (is_discrete) {
                GPUInfo gpu;
                gpu.name = name;
                gpu.available = true;

                // Get driver version
                gpu.driver_version = get_nvidia_driver_version();
                if (gpu.driver_version.empty()) {
                    gpu.driver_version = "Unknown";
                }

                // Get VRAM
                double vram = get_nvidia_vram();
                if (vram > 0.0) {
                    gpu.vram_gb = vram;
                }

                gpus.push_back(gpu);
            }
        }
    }

    if (gpus.empty()) {
        GPUInfo gpu;
        gpu.available = false;
        gpu.error = "No NVIDIA discrete GPU found";
        gpus.push_back(gpu);
    }

    return gpus;
}

NPUInfo LinuxSystemInfo::get_npu_device() {
    NPUInfo npu;
    npu.name = "AMD NPU";
    npu.available = false;

    fs::path accel_path = "/sys/class/accel";
    if (!fs::exists(accel_path) || !fs::is_directory(accel_path)) {
        npu.error = "No NPU device found";
        return npu;
    }

    for (const auto& entry : fs::directory_iterator(accel_path)) {
        if (!entry.is_directory() && !entry.is_symlink()) {
            continue;
        }
        fs::path driver_link = entry.path() / "device" / "driver";
        if (!fs::exists(driver_link)) {
            continue;
        }
        fs::path driver_path = fs::read_symlink(driver_link);
        std::string driver_name = driver_path.filename().string();

        if (driver_name != "amdxdna") {
            continue;
        }

        npu.available = true;

        fs::path vbnv_file = entry.path() / "device" / "vbnv";
        if (fs::exists(vbnv_file)) {
            std::ifstream vbnv_stream(vbnv_file);
            if (vbnv_stream.is_open()) {
                std::string vbnv_content;
                std::getline(vbnv_stream, vbnv_content);
                vbnv_stream.close();

                if (!vbnv_content.empty()) {
                    npu.name = "AMD NPU (" + vbnv_content + ")";
                }
            }
        }

                // Try to query TOPs and Power Mode via IOCTL
                std::string accel_dev = "/dev/accel/accel0";
                if (fs::exists(accel_dev)) {
                    int fd = open(accel_dev.c_str(), O_RDWR);
                    if (fd >= 0) {
                        // Query Resource Info (TOPs)
                        amdxdna_drm_get_resource_info res_info = {};
                        amdxdna_drm_get_info get_info = {};
                        get_info.param = DRM_AMDXDNA_QUERY_RESOURCE_INFO;
                        get_info.buffer_size = sizeof(res_info);
                        get_info.buffer = (uintptr_t)&res_info;

                        if (ioctl(fd, DRM_IOCTL_AMDXDNA_GET_INFO, &get_info) >= 0) {
                            npu.tops_max = res_info.npu_tops_max;
                            npu.tops_curr = res_info.npu_tops_curr;
                        }

                        // Query Sensors (Utilization)
                        amdxdna_drm_query_sensor sensors[16] = {};
                        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) {
                            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) {
                                npu.utilization = (float)(usage_sum / usage_count);
                            }
                        }

                        // Query Power Mode
                        amdxdna_drm_get_power_mode pwr_info = {};
                        get_info.param = DRM_AMDXDNA_GET_POWER_MODE;
                        get_info.buffer_size = sizeof(pwr_info);
                        get_info.buffer = (uintptr_t)&pwr_info;

                        if (ioctl(fd, DRM_IOCTL_AMDXDNA_GET_INFO, &get_info) >= 0) {
                            static const std::map<int, std::string> POWER_MODE_MAP = {
                                {POWER_MODE_DEFAULT, "DEFAULT"},
                                {POWER_MODE_LOW, "LOW"},
                                {POWER_MODE_MEDIUM, "MEDIUM"},
                                {POWER_MODE_HIGH, "HIGH"},
                                {POWER_MODE_TURBO, "TURBO"}
                            };
                            auto it = POWER_MODE_MAP.find(pwr_info.power_mode);
                            if (it != POWER_MODE_MAP.end()) {
                                npu.power_mode = it->second;
                            } else {
                                npu.power_mode = "Unknown (" + std::to_string(pwr_info.power_mode) + ")";
                            }
                        }
                        close(fd);
                    }
                }
        break;
    }

    if (!npu.available) {
        npu.error = "No NPU device found with amdxdna driver";
    }

    return npu;
}

std::vector<GPUInfo> LinuxSystemInfo::detect_amd_gpus(const std::string& gpu_type) {
    std::vector<GPUInfo> gpus;
    std::string kfd_path = "/sys/class/kfd/kfd/topology/nodes";

    if (!fs::exists(kfd_path)) {
        GPUInfo gpu;
        gpu.available = false;
        gpu.error = "No KFD nodes found (AMD GPU driver not loaded or no GPU present)";
        gpus.push_back(gpu);
        return gpus;
    }

    for (const auto& node_entry : fs::directory_iterator(kfd_path)) {
        if (!node_entry.is_directory()) continue;

        std::string node_path = node_entry.path().string();
        std::string properties_file = node_path + "/properties";

        if (!fs::exists(properties_file)) continue;

        std::ifstream props(properties_file);
        if (!props.is_open()) continue;

        std::string line;
        std::string drm_render_minor;
        std::string gfx_target_version;

        bool is_gpu = false;

        while (std::getline(props, line)) {
            if (line.find("gfx_target_version") == 0) {
                gfx_target_version = line.substr(line.find(" ") + 1);
                gfx_target_version.erase(gfx_target_version.find_last_not_of(" \t\n\r") + 1);
                if (!gfx_target_version.empty() && std::stoi(gfx_target_version) != 0) {
                    is_gpu = true;
                }
            } else if (line.find("drm_render_minor") == 0) {
                drm_render_minor = line.substr(line.find(" ") + 1);
                drm_render_minor.erase(drm_render_minor.find_last_not_of(" \t\n\r") + 1);
            }
        }
        props.close();

        if (!is_gpu || drm_render_minor.empty() || drm_render_minor == "-1")
            continue;

        bool is_integrated = get_amd_is_igpu(drm_render_minor);
        if ((gpu_type == "integrated" && !is_integrated) || (gpu_type == "discrete" && is_integrated)) continue;

        GPUInfo gpu;
        gpu.name = gfx_target_version;
        gpu.available = true;

        // Get VRAM and GTT for GPUs
        gpu.vram_gb = get_amd_vram(drm_render_minor);
        gpu.virtual_gb = get_amd_gtt(drm_render_minor);

        gpus.push_back(gpu);
    }

    if (gpus.empty()) {
        GPUInfo gpu;
        gpu.available = false;
        gpu.error = "No AMD " + gpu_type + " GPU found in KFD nodes";
        gpus.push_back(gpu);
    }

    return gpus;
}

std::string LinuxSystemInfo::get_nvidia_driver_version() {
    // Try nvidia-smi first
    FILE* pipe = popen("nvidia-smi --query-gpu=driver_version --format=csv,noheader,nounits 2>/dev/null", "r");
    if (pipe) {
        char buffer[128];
        if (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
            std::string version = buffer;
            // Remove newline
            if (!version.empty() && version.back() == '\n') {
                version.pop_back();
            }
            pclose(pipe);
            if (!version.empty() && version != "N/A") {
                return version;
            }
        }
        pclose(pipe);
    }

    // Fallback: Try /proc/driver/nvidia/version
    std::ifstream file("/proc/driver/nvidia/version");
    if (file.is_open()) {
        std::string line;
        while (std::getline(file, line)) {
            // Look for "Kernel Module  XXX.XX.XX"
            if (line.find("Kernel Module") != std::string::npos) {
                std::regex version_regex(R"(Kernel Module\s+(\d+\.\d+(?:\.\d+)?))");
                std::smatch match;
                if (std::regex_search(line, match, version_regex)) {
                    return match[1].str();
                }
            }
        }
    }

    return "";
}

double LinuxSystemInfo::get_nvidia_vram() {
    FILE* pipe = popen("nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null", "r");
    if (!pipe) {
        return 0.0;
    }

    char buffer[128];
    if (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
        std::string vram_str = buffer;
        pclose(pipe);

        try {
            // nvidia-smi returns MB
            double vram_mb = std::stod(vram_str);
            return std::round(vram_mb / 1024.0 * 10.0) / 10.0;  // Convert to GB, round to 1 decimal
        } catch (...) {
            return 0.0;
        }
    }
    pclose(pipe);

    return 0.0;
}

double LinuxSystemInfo::get_ttm_gb() {
    std::string ttm_path = "/sys/module/ttm/parameters/pages_limit";
    std::ifstream sysfs_file(ttm_path);

    if (!sysfs_file.is_open()) {
        return 0.0;
    }

    std::string page_limit_str;
    std::getline(sysfs_file, page_limit_str);
    sysfs_file.close();

    try {
        uint64_t page_limit = std::stoull(page_limit_str);
        return std::round(page_limit / ((1024.0 * 1024.0 * 1024.0) / 4096) * 10.0) / 10.0;
    } catch (...) {
        return 0.0;
    }
}

bool LinuxSystemInfo::get_amd_is_igpu(const std::string& drm_render_minor) {
    std::string device_path = "/sys/class/drm/renderD" + drm_render_minor + "/device/";
    std::string board_info_path = device_path + "board_info";
    return !(fs::exists(board_info_path) && fs::is_regular_file(board_info_path));
}

double LinuxSystemInfo::parse_memory_sysfs(const std::string& drm_render_minor, const std::string& fname){
    // Try device-specific path first
    std::string sysfs_path = "/sys/class/drm/renderD" + drm_render_minor + "/device/" + fname;

    if (!fs::exists(sysfs_path))
        return 0.0;

    std::ifstream sysfs_file(sysfs_path);
    std::string memory_str;
    std::getline(sysfs_file, memory_str);
    sysfs_file.close();

    try {
        uint64_t memory_bytes = std::stoull(memory_str);
        return std::round(memory_bytes / (1024.0 * 1024.0 * 1024.0) * 10.0) / 10.0;
    } catch (...) {
        return 0.0;
    }
}

double LinuxSystemInfo::get_amd_gtt(const std::string& drm_render_minor){
    return parse_memory_sysfs(drm_render_minor, "mem_info_gtt_total");
}

double LinuxSystemInfo::get_amd_vram(const std::string& drm_render_minor) {
    return parse_memory_sysfs(drm_render_minor, "mem_info_vram_total");
}

json LinuxSystemInfo::get_system_info_dict() {
    json info = SystemInfo::get_system_info_dict();  // Get base fields
    info["Processor"] = get_processor_name();
    info["Physical Memory"] = get_physical_memory();
    return info;
}

std::string LinuxSystemInfo::get_os_version() {
    // Get detailed Linux version (similar to Python's platform.platform())
    std::string result = "Linux";

    // Get kernel version from /proc/version
    std::string kernel = "unknown_kernel";

    std::ifstream file("/proc/version");
    if (file.is_open()){
        std::string line;
        std::getline(file, line);

        const std::string tag = "version ";
        size_t pos = line.find(tag);
        if (pos != std::string::npos){
            pos += tag.size();
            size_t end = line.find(' ', pos);
            kernel = line.substr(pos, end - pos);
        }
    }
    result += "-" + kernel;

    // Try to get distribution info from /etc/os-release
    std::ifstream os_release("/etc/os-release");
    if (os_release.is_open()) {
        std::string line;
        std::string distro_name, distro_version;
        while (std::getline(os_release, line)) {
            if (line.find("NAME=") == 0) {
                distro_name = line.substr(5);
                // Remove quotes
                distro_name.erase(std::remove(distro_name.begin(), distro_name.end(), '"'), distro_name.end());
            } else if (line.find("VERSION_ID=") == 0) {
                distro_version = line.substr(11);
                // Remove quotes
                distro_version.erase(std::remove(distro_version.begin(), distro_version.end(), '"'), distro_version.end());
            }
        }

        if (!distro_name.empty()) {
            result += " (" + distro_name;
            if (!distro_version.empty()) {
                result += " " + distro_version;
            }
            result += ")";
        }
    }

    return result;
}

std::string LinuxSystemInfo::get_processor_name() {
    FILE* pipe = popen("lscpu 2>/dev/null", "r");
    if (!pipe) {
        return "ERROR - Failed to execute lscpu";
    }

    char buffer[256];
    std::string output;
    while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
        output += buffer;
    }
    pclose(pipe);

    std::istringstream iss(output);
    std::string line;
    while (std::getline(iss, line)) {
        if (line.find("Model name:") != std::string::npos) {
            size_t pos = line.find(":");
            if (pos != std::string::npos) {
                std::string name = line.substr(pos + 1);
                // Trim whitespace
                size_t start = name.find_first_not_of(" \t");
                size_t end = name.find_last_not_of(" \t");
                if (start != std::string::npos && end != std::string::npos) {
                    return name.substr(start, end - start + 1);
                }
            }
        }
    }

    return "ERROR - Processor name not found";
}

std::string LinuxSystemInfo::get_physical_memory() {
    std::string token;
    std::ifstream file("/proc/meminfo");

    //Step through each token in the file
    while(file >> token) {
        if(token == "MemTotal:") {
            // Get the token after "MemTotal:"
            if(double mem; file >> mem) {
                std::ostringstream oss;
                oss << std::fixed << std::setprecision(2) << std::round(mem / 1024.0 / 1024.0 * 100.0) / 100.0 << " GB";
                return oss.str();
            }
            break;
        }
        // Skip the line if key/token isn't found.
        file.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }
    return "ERROR - Physical memory not found";
}

#endif // __linux__

// ============================================================================
// macOS implementation
// ============================================================================

#ifdef __APPLE__

CPUInfo MacOSSystemInfo::get_cpu_device() {
    CPUInfo cpu;
    cpu.available = false;

    // Initialize numeric values to -1 to distinguish between "0" and "Failed to fetch"
    cpu.cores = -1;
    cpu.threads = -1;
    cpu.max_clock_speed_mhz = 0;

    size_t size;
    char buffer[256];

    size = sizeof(buffer);
    if (sysctlbyname("machdep.cpu.brand_string", buffer, &size, nullptr, 0) == 0) {
        cpu.name = buffer;
        cpu.available = true;
    } else {
        cpu.name = "Unknown Apple Processor";
        cpu.error = "sysctl failed for machdep.cpu.brand_string";
    }

    int cores = 0;
    size = sizeof(cores);
    if (sysctlbyname("hw.physicalcpu", &cores, &size, nullptr, 0) == 0) {
        cpu.cores = cores;
    } else {
        cpu.error += " | Failed to get physical cores";
    }

    int threads = 0;
    size = sizeof(threads);
    if (sysctlbyname("hw.logicalcpu", &threads, &size, nullptr, 0) == 0) {
        cpu.threads = threads;
    } else {
        cpu.error += " | Failed to get logical threads";
    }

    // 4. Get Max Clock Speed
    uint64_t freq = 0;
    size = sizeof(freq);
    if (sysctlbyname("hw.cpufrequency_max", &freq, &size, nullptr, 0) == 0) {
        //Calculation of hz to mhz
        cpu.max_clock_speed_mhz = (freq > 0) ? (uint32_t)(freq / 1000000) : 0;
    } else {
        cpu.error += " | Failed to get maximum frequency";
    }

    return cpu;
}

GPUInfo MacOSSystemInfo::get_amd_igpu_device() {
    GPUInfo gpu;
    gpu.available = false;
    gpu.error = "AMD integrated GPUs not detected on macOS";
    return gpu;
}

std::vector<GPUInfo> MacOSSystemInfo::get_amd_dgpu_devices() {
    GPUInfo gpu;
    gpu.available = false;
    gpu.error = "AMD discrete GPUs not detected on macOS";
    return {gpu};
}

std::vector<GPUInfo> MacOSSystemInfo::get_nvidia_dgpu_devices() {
    GPUInfo gpu;
    gpu.available = false;
    gpu.error = "NVIDIA GPUs not detected on macOS";
    return {gpu};
}

NPUInfo MacOSSystemInfo::get_npu_device() {
    NPUInfo npu;
    npu.name = "AMD NPU";
    npu.available = false;
    npu.error = "NPU not supported on macOS (Ryzen AI NPUs are Windows/Linux only)";
    return npu;
}

json MacOSSystemInfo::get_system_info_dict() {
    json info = SystemInfo::get_system_info_dict();  // Get base fields (includes OS Version)
    info["Processor"] = get_processor_name();
    info["Physical Memory"] = get_physical_memory();
    return info;
}

std::string MacOSSystemInfo::get_os_version() {
    std::string result = "macOS";

    // Get macOS product version (e.g., "14.3.1")
    FILE* pipe = popen("sw_vers -productVersion 2>/dev/null", "r");
    if (pipe) {
        char buffer[128];
        if (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
            std::string version = buffer;
            // Trim trailing newline
            while (!version.empty() && (version.back() == '\n' || version.back() == '\r')) {
                version.pop_back();
            }
            if (!version.empty()) {
                result += " " + version;
            }
        }
        pclose(pipe);
    }

    // Append Darwin kernel version
    char kernel_buf[256];
    size_t size = sizeof(kernel_buf);
    if (sysctlbyname("kern.osrelease", kernel_buf, &size, nullptr, 0) == 0) {
        result += " Darwin Kernel Version " + std::string(kernel_buf);
    }

    return result;
}

std::string MacOSSystemInfo::get_processor_name() {
    char buffer[256];
    size_t size = sizeof(buffer);
    if (sysctlbyname("machdep.cpu.brand_string", buffer, &size, nullptr, 0) == 0) {
        return std::string(buffer);
    }
    return "Unknown Apple Processor";
}

std::string MacOSSystemInfo::get_physical_memory() {
    uint64_t mem_bytes = 0;
    size_t size = sizeof(mem_bytes);
    if (sysctlbyname("hw.memsize", &mem_bytes, &size, nullptr, 0) == 0) {
        double mem_gb = mem_bytes / (1024.0 * 1024.0 * 1024.0);
        // Round to nearest integer for clean display
        int rounded_gb = static_cast<int>(mem_gb + 0.5);
        return std::to_string(rounded_gb) + " GB";
    }
    return "Unknown";
}

#endif // __APPLE__

// ============================================================================
// Cache implementation
// ============================================================================

// Static state for in-memory system-info cache (hardware + recipes)
static std::mutex s_system_info_mutex;
static json s_cached_system_info;
static bool s_hardware_computed = false;
static bool s_recipes_computed = false;

json SystemInfoCache::get_system_info_with_cache() {
    std::lock_guard<std::mutex> lock(s_system_info_mutex);

    // Return fully cached result if both hardware and recipes are computed
    if (s_hardware_computed && s_recipes_computed) {
        return s_cached_system_info;
    }

    // Compute hardware if not cached
    if (!s_hardware_computed) {
        json system_info;

        // Top-level try-catch to ensure system info collection NEVER crashes Lemonade
        try {
            auto sys_info = create_system_info();

            // Get system info (OS, Processor, Memory, etc.)
            try {
                system_info = sys_info->get_system_info_dict();
            } catch (...) {
                system_info["OS Version"] = "Unknown";
            }

            // Get device information - handles its own exceptions internally
            system_info["devices"] = sys_info->get_device_dict();

            s_cached_system_info = system_info;

        } catch (const std::exception& e) {
            // Catastrophic failure - return minimal info but don't crash
            LOG(ERROR, "Server") << "System info failed: " << e.what() << std::endl;
            s_cached_system_info = {
                {"OS Version", "Unknown"},
                {"error", e.what()},
                {"devices", json::object()}
            };
        } catch (...) {
            LOG(ERROR, "Server") << "System info failed with unknown error" << std::endl;
            s_cached_system_info = {
                {"OS Version", "Unknown"},
                {"error", "Unknown error"},
                {"devices", json::object()}
            };
        }

        s_hardware_computed = true;
    }

    // Compute recipes if not cached (or invalidated)
    if (!s_recipes_computed) {
        try {
            auto sys_info = create_system_info();
            json devices = s_cached_system_info.contains("devices")
                ? s_cached_system_info["devices"] : json::object();
            s_cached_system_info["recipes"] = sys_info->build_recipes_info(devices);
        } catch (const std::exception& e) {
            LOG(ERROR, "Server") << "Recipe detection failed: " << e.what() << std::endl;
        } catch (...) {
            LOG(ERROR, "Server") << "Recipe detection failed with unknown error" << std::endl;
        }
        s_recipes_computed = true;
    }

    return s_cached_system_info;
}

void SystemInfoCache::invalidate_recipes() {
    std::lock_guard<std::mutex> lock(s_system_info_mutex);
    s_recipes_computed = false;
}

FlmStatus SystemInfoCache::get_flm_status() {
    json info = get_system_info_with_cache();

    // Navigate to recipes.flm.backends.npu
    if (info.contains("recipes") &&
        info["recipes"].contains("flm") &&
        info["recipes"]["flm"].contains("backends") &&
        info["recipes"]["flm"]["backends"].contains("npu")) {
        const auto& npu = info["recipes"]["flm"]["backends"]["npu"];
        return {
            npu.value("state", "unsupported"),
            npu.value("version", ""),
            npu.value("message", ""),
            npu.value("action", "")
        };
    }

    return {"unsupported", "", "FLM recipe not found in system info", ""};
}


bool SystemInfo::is_running_under_systemd() {
#ifdef _WIN32
    return false;
#else
    const char* disable_journal = std::getenv("LEMONADE_DISABLE_SYSTEMD_JOURNAL");
    if (disable_journal && (std::string(disable_journal) == "1" || std::string(disable_journal) == "true")) {
        return false;
    }

#ifdef HAVE_SYSTEMD
    // Use systemd journal only when actually running as lemonade-server.service.
    // sd_pid_get_unit() reads the process's cgroup assignment (not environment variables),
    // so it cannot give false positives from inherited env vars like JOURNAL_STREAM or
    // INVOCATION_ID, both of which are inherited by all child processes in a systemd session.
    char* unit_name = nullptr;
    if (sd_pid_get_unit(0, &unit_name) >= 0) {
        const char* service_name_env = std::getenv("LEMONADE_SYSTEMD_UNIT");
        const char* service_name = service_name_env ? service_name_env : LEMONADE_SYSTEMD_UNIT_NAME;
        bool is_service = (strcmp(unit_name, service_name) == 0);
        free(unit_name);
        return is_service;
    }
#endif

    const char* journal_stream = std::getenv("JOURNAL_STREAM");
    const char* invocation_id = std::getenv("INVOCATION_ID");
    return (journal_stream || invocation_id) && !isatty(STDOUT_FILENO);
#endif
}

} // namespace lemon
