#include <grove/ModuleLoader.h>
#include <grove/JsonDataNode.h>
#include <grove/IOFactory.h>

#include "services/LLMService.hpp"
#include "services/StorageService.hpp"
#include "services/PlatformService.hpp"
#include "services/VoiceService.hpp"
#include "shared/mcp/MCPServer.hpp"
#include "shared/tools/FileSystemTools.hpp"
#include "shared/tools/MCPServerTools.hpp"
#include "shared/llm/ToolRegistry.hpp"

#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>

#include <chrono>
#include <thread>
#include <csignal>
#include <filesystem>
#include <fstream>
#include <map>
#include <cstring>
#include <iostream>
#include <atomic>
#include <functional>

namespace fs = std::filesystem;

// Helper to clone IDataNode (workaround for missing clone() method)
std::unique_ptr<grove::IDataNode> cloneDataNode(grove::IDataNode* node) {
    if (!node) return nullptr;
    // Cast to JsonDataNode to access the JSON data
    auto* jsonNode = dynamic_cast<grove::JsonDataNode*>(node);
    if (jsonNode) {
        return std::make_unique<grove::JsonDataNode>(
            jsonNode->getName(),
            jsonNode->getJsonData()
        );
    }
    // Fallback: create empty node
    return std::make_unique<grove::JsonDataNode>("data");
}

// Global flag for clean shutdown
static volatile bool g_running = true;

void signalHandler(int signal) {
    spdlog::info("Signal {} recu, arret en cours...", signal);
    g_running = false;
}

// Simple file watcher for hot-reload
class FileWatcher {
public:
    void watch(const std::string& path) {
        if (fs::exists(path)) {
            m_lastModified[path] = fs::last_write_time(path);
        }
    }

    bool hasChanged(const std::string& path) {
        if (!fs::exists(path)) return false;

        auto currentTime = fs::last_write_time(path);
        auto it = m_lastModified.find(path);

        if (it == m_lastModified.end()) {
            m_lastModified[path] = currentTime;
            return false;
        }

        if (currentTime != it->second) {
            it->second = currentTime;
            return true;
        }
        return false;
    }

private:
    std::unordered_map<std::string, fs::file_time_type> m_lastModified;
};

// Load JSON config file
std::unique_ptr<grove::JsonDataNode> loadConfig(const std::string& path) {
    if (fs::exists(path)) {
        std::ifstream file(path);
        nlohmann::json j;
        file >> j;
        auto config = std::make_unique<grove::JsonDataNode>("config", j);
        spdlog::info("Config chargee: {}", path);
        return config;
    } else {
        spdlog::warn("Config non trouvee: {}, utilisation des defauts", path);
        return std::make_unique<grove::JsonDataNode>("config");
    }
}

// Module entry in our simple manager
struct ModuleEntry {
    std::string name;
    std::string configFile;
    std::string path;
    std::unique_ptr<grove::ModuleLoader> loader;
    std::unique_ptr<grove::IIO> io;
    grove::IModule* module = nullptr;
};

// Message router between modules and services
class MessageRouter {
public:
    using MessageCallback = std::function<void(const std::string& topic, grove::IDataNode* data)>;

    void addModuleIO(const std::string& name, grove::IIO* io) {
        m_moduleIOs[name] = io;
    }

    void addServiceIO(const std::string& name, grove::IIO* io) {
        m_serviceIOs[name] = io;
    }

    void setMessageCallback(MessageCallback callback) {
        m_callback = callback;
    }

    void routeMessages() {
        // Collect all messages from modules and services
        std::vector<grove::Message> messages;

        for (auto& [name, io] : m_moduleIOs) {
            if (!io) continue;
            while (io->hasMessages() > 0) {
                messages.push_back(io->pullMessage());
            }
        }

        for (auto& [name, io] : m_serviceIOs) {
            if (!io) continue;
            while (io->hasMessages() > 0) {
                messages.push_back(io->pullMessage());
            }
        }

        // Route messages to appropriate destinations
        for (auto& msg : messages) {
            // Call callback if set
            if (m_callback && msg.data) {
                m_callback(msg.topic, msg.data.get());
            }

            // Determine destination based on topic prefix
            std::string prefix = msg.topic.substr(0, msg.topic.find(':'));

            // Route to services
            if (prefix == "llm" || prefix == "storage" || prefix == "platform" || prefix == "voice") {
                for (auto& [name, io] : m_serviceIOs) {
                    if (io && msg.data) {
                        io->publish(msg.topic, cloneDataNode(msg.data.get()));
                    }
                }
            }

            // Route to modules (broadcast)
            for (auto& [name, io] : m_moduleIOs) {
                if (io && msg.data) {
                    io->publish(msg.topic, cloneDataNode(msg.data.get()));
                }
            }
        }
    }

private:
    std::map<std::string, grove::IIO*> m_moduleIOs;
    std::map<std::string, grove::IIO*> m_serviceIOs;
    MessageCallback m_callback;
};

// Run AISSIA as MCP server (stdio mode)
int runMCPServer() {
    // Log to stderr so stdout stays clean for JSON-RPC
    auto logger = spdlog::stderr_color_mt("MCPServer");
    spdlog::set_default_logger(logger);
    spdlog::set_level(spdlog::level::info);

    spdlog::info("AISSIA MCP Server starting...");

    // === Initialize Services ===

    // 1. LLMService (PRIORITY: for chat_with_aissia)
    auto llmService = std::make_unique<celuna::LLMService>();
    if (!llmService->loadConfig("config/llm.json")) {
        spdlog::warn("Failed to load LLM config, chat_with_aissia will be unavailable");
    }
    llmService->initialize(nullptr);  // No IIO in MCP mode
    llmService->initializeTools();     // Load internal tools + MCP tools

    // 2. StorageService (for save_memory/search_memories)
    auto storageService = std::make_unique<celuna::StorageService>();
    storageService->initialize(nullptr);

    // 3. VoiceService (for TTS/STT)
    auto voiceService = std::make_unique<celuna::VoiceService>();
    // Note: Voice config is optional
    voiceService->initialize(nullptr);

    // === Create Tool Registry ===
    celuna::ToolRegistry registry;

    // Register get_current_time tool
    registry.registerTool(
        "get_current_time",
        "Get the current date and time",
        {{"type", "object"}, {"properties", nlohmann::json::object()}},
        [](const nlohmann::json& input) -> nlohmann::json {
            std::time_t now = std::time(nullptr);
            std::tm* tm = std::localtime(&now);
            char buffer[64];
            std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tm);
            return {{"time", buffer}};
        }
    );

    // Register FileSystem tools
    for (const auto& toolDef : celuna::tools::FileSystemTools::getToolDefinitions()) {
        std::string toolName = toolDef["name"].get<std::string>();
        registry.registerTool(
            toolName,
            toolDef["description"].get<std::string>(),
            toolDef["input_schema"],
            [toolName](const nlohmann::json& input) -> nlohmann::json {
                return celuna::tools::FileSystemTools::execute(toolName, input);
            }
        );
    }

    // === Register AISSIA Tools (Phase 8) ===
    celuna::tools::MCPServerTools aissiaTools(
        llmService.get(),
        storageService.get(),
        voiceService.get()
    );

    for (const auto& toolDef : aissiaTools.getToolDefinitions()) {
        registry.registerTool(toolDef);
    }

    spdlog::info("Registered {} tools total", registry.size());

    // Create and run MCP server
    celuna::mcp::MCPServer server(registry);
    server.setServerInfo("aissia", "0.2.0");
    server.run();

    return 0;
}

// Flag for interactive mode
static std::atomic<bool> g_interactive{false};
static grove::IIO* g_interactiveIO = nullptr;

// Stdin reader thread for interactive mode
void stdinReaderThread() {
    std::string line;
    std::cout << "\n[AISSIA] Mode interactif active. Tapez vos messages (quit pour quitter):\n> " << std::flush;

    while (g_running && std::getline(std::cin, line)) {
        if (line == "quit" || line == "exit" || line == "q") {
            g_running = false;
            break;
        }

        if (line.empty()) {
            std::cout << "> " << std::flush;
            continue;
        }

        // Send message to LLM service via IIO (from InteractiveClient, not LLMService)
        if (g_interactiveIO) {
            auto request = std::make_unique<grove::JsonDataNode>("request");
            request->setString("query", line);
            request->setString("conversation_id", "interactive");
            g_interactiveIO->publish("llm:request", std::move(request));
            spdlog::info("Message envoye: {}", line);
        }

        std::cout << "> " << std::flush;
    }
}

int main(int argc, char* argv[]) {
    // Check for MCP server mode
    if (argc > 1 && (std::strcmp(argv[1], "--mcp-server") == 0 ||
                     std::strcmp(argv[1], "--mcp") == 0)) {
        return runMCPServer();
    }

    // Check for test runner mode
    bool testMode = false;
    for (int i = 1; i < argc; ++i) {
        if (std::strcmp(argv[i], "--run-tests") == 0 ||
            std::strcmp(argv[i], "--test") == 0) {
            testMode = true;
            break;
        }
    }

    // Check for interactive mode
    for (int i = 1; i < argc; ++i) {
        if (std::strcmp(argv[i], "--interactive") == 0 ||
            std::strcmp(argv[i], "-i") == 0) {
            g_interactive = true;
        }
    }

    // Setup logging
    auto console = spdlog::stdout_color_mt("Aissia");
    spdlog::set_default_logger(console);
    spdlog::set_level(spdlog::level::debug);
    spdlog::set_pattern("[%H:%M:%S.%e] [%n] [%^%l%$] %v");

    spdlog::info("========================================");
    if (testMode) {
        spdlog::info("  AISSIA - Integration Test Mode");
    } else {
        spdlog::info("  AISSIA - Assistant Personnel IA");
    }
    spdlog::info("  Powered by GroveEngine");
    spdlog::info("  Architecture: Services + Hot-Reload Modules");
    spdlog::info("  (Use --mcp-server to run as MCP server)");
    spdlog::info("  (Use --run-tests to run integration tests)");
    spdlog::info("========================================");

    // Signal handling
    std::signal(SIGINT, signalHandler);
    std::signal(SIGTERM, signalHandler);

    // Paths - try ./modules/ first, fallback to ./build/modules/
    std::string modulesDir = "./modules/";
    if (!fs::exists(modulesDir) || fs::is_empty(modulesDir)) {
        if (fs::exists("./build/modules/")) {
            modulesDir = "./build/modules/";
            spdlog::info("Using modules from: {}", modulesDir);
        }
    }
    const std::string configDir = "./config/";

    // =========================================================================
    // Infrastructure Services (non hot-reloadable)
    // =========================================================================
    spdlog::info("Initialisation des services infrastructure...");

    // Create IIO for services
    auto llmIO = grove::IOFactory::create("intra", "LLMService");
    auto storageIO = grove::IOFactory::create("intra", "StorageService");
    auto platformIO = grove::IOFactory::create("intra", "PlatformService");
    auto voiceIO = grove::IOFactory::create("intra", "VoiceService");

    // LLM Service
    celuna::LLMService llmService;
    llmService.initialize(llmIO.get());
    llmService.loadConfig(configDir + "ai.json");

    // Register default tools
    llmService.registerTool(
        "get_current_time",
        "Obtient l'heure actuelle",
        {{"type", "object"}, {"properties", nlohmann::json::object()}},
        [](const nlohmann::json& input) -> nlohmann::json {
            std::time_t now = std::time(nullptr);
            std::tm* tm = std::localtime(&now);
            char buffer[64];
            std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tm);
            return {{"time", buffer}};
        }
    );

    // Storage Service
    celuna::StorageService storageService;
    storageService.initialize(storageIO.get());
    {
        auto storageConfig = loadConfig(configDir + "storage.json");
        std::string dbPath = storageConfig->getString("database_path", "./data/aissia.db");
        std::string journalMode = storageConfig->getString("journal_mode", "WAL");
        int busyTimeout = storageConfig->getInt("busy_timeout_ms", 5000);
        storageService.openDatabase(dbPath, journalMode, busyTimeout);
    }

    // Platform Service
    celuna::PlatformService platformService;
    platformService.initialize(platformIO.get());
    {
        auto monitorConfig = loadConfig(configDir + "monitoring.json");
        int pollInterval = monitorConfig->getInt("poll_interval_ms", 1000);
        int idleThreshold = monitorConfig->getInt("idle_threshold_seconds", 300);
        platformService.configure(pollInterval, idleThreshold);
    }

    // Voice Service
    celuna::VoiceService voiceService;
    voiceService.initialize(voiceIO.get());
    {
        // Load voice.json directly as nlohmann::json for Phase 7 STT
        std::ifstream voiceFile(configDir + "voice.json");
        nlohmann::json voiceConfigJson;
        voiceFile >> voiceConfigJson;

        // Configure TTS (legacy API)
        if (voiceConfigJson.contains("tts")) {
            auto tts = voiceConfigJson["tts"];
            bool enabled = tts.value("enabled", true);
            int rate = tts.value("rate", 0);
            int volume = tts.value("volume", 80);
            voiceService.configureTTS(enabled, rate, volume);
        }

        // Configure STT (Phase 7 new API)
        if (voiceConfigJson.contains("stt")) {
            voiceService.configureSTT(voiceConfigJson["stt"]);
        }
    }

    spdlog::info("Services initialises: LLM={}, Storage={}, Platform={}, Voice={}",
                llmService.isHealthy() ? "OK" : "FAIL",
                storageService.isHealthy() ? "OK" : "FAIL",
                platformService.isHealthy() ? "OK" : "FAIL",
                voiceService.isHealthy() ? "OK" : "FAIL");

    // Create separate IO for interactive mode (to avoid self-delivery skip)
    std::unique_ptr<grove::IIO> interactiveIO;
    std::unique_ptr<std::thread> stdinThread;
    if (g_interactive) {
        spdlog::info("Mode interactif active (--interactive)");
        interactiveIO = grove::IOFactory::create("intra", "InteractiveClient");
        g_interactiveIO = interactiveIO.get();

        // Subscribe to LLM responses to display them
        grove::SubscriptionConfig subConfig;
        interactiveIO->subscribe("llm:response", subConfig);
        interactiveIO->subscribe("llm:error", subConfig);

        stdinThread = std::make_unique<std::thread>(stdinReaderThread);
    }

    // =========================================================================
    // Hot-Reloadable Modules
    // =========================================================================
    std::map<std::string, ModuleEntry> modules;
    FileWatcher watcher;
    MessageRouter router;

    // Register service IOs with router
    router.addServiceIO("LLMService", llmIO.get());
    router.addServiceIO("StorageService", storageIO.get());
    router.addServiceIO("PlatformService", platformIO.get());
    router.addServiceIO("VoiceService", voiceIO.get());

    // Setup interactive mode callback to display LLM responses
    if (g_interactive) {
        router.setMessageCallback([](const std::string& topic, grove::IDataNode* data) {
            if (topic == "llm:response" && data) {
                auto* jsonNode = dynamic_cast<grove::JsonDataNode*>(data);
                if (jsonNode) {
                    std::string text = jsonNode->getString("text", "");
                    if (!text.empty()) {
                        std::cout << "\n[AISSIA] " << text << "\n> " << std::flush;
                    }
                }
            } else if (topic == "llm:error" && data) {
                auto* jsonNode = dynamic_cast<grove::JsonDataNode*>(data);
                if (jsonNode) {
                    std::string message = jsonNode->getString("message", "Unknown error");
                    std::cout << "\n[ERREUR] " << message << "\n> " << std::flush;
                }
            }
        });
    }

    // Liste des modules a charger (sans infrastructure)
    std::vector<std::pair<std::string, std::string>> moduleList;

    if (testMode) {
        // In test mode, only load TestRunnerModule
        moduleList = {
            {"TestRunnerModule", "test_runner.json"}
        };
    } else {
        // Normal mode: load all regular modules
        moduleList = {
            {"SchedulerModule", "scheduler.json"},
            {"NotificationModule", "notification.json"},
            {"MonitoringModule", "monitoring.json"},
            {"AIModule", "ai.json"},
            {"VoiceModule", "voice.json"},
            {"StorageModule", "storage.json"},
            {"WebModule", "web.json"},
        };
    }

    // Charger les modules
    for (const auto& [moduleName, configFile] : moduleList) {
        std::string modulePath = modulesDir + "lib" + moduleName + ".so";

        if (!fs::exists(modulePath)) {
            spdlog::warn("{} non trouve: {}", moduleName, modulePath);
            continue;
        }

        ModuleEntry entry;
        entry.name = moduleName;
        entry.configFile = configFile;
        entry.path = modulePath;
        entry.loader = std::make_unique<grove::ModuleLoader>();
        entry.io = grove::IOFactory::create("intra", moduleName);

        auto modulePtr = entry.loader->load(modulePath, moduleName);
        if (!modulePtr) {
            spdlog::error("Echec du chargement: {}", moduleName);
            continue;
        }

        // Configure
        auto config = loadConfig(configDir + configFile);
        modulePtr->setConfiguration(*config, entry.io.get(), nullptr);

        entry.module = modulePtr.release();
        watcher.watch(modulePath);

        // Register with router
        router.addModuleIO(moduleName, entry.io.get());

        spdlog::info("{} charge et configure", moduleName);
        modules[moduleName] = std::move(entry);
    }

    if (modules.empty()) {
        spdlog::error("Aucun module charge! Build les modules: cmake --build build --target modules");
        return 1;
    }

    // =========================================================================
    // Main Loop
    // =========================================================================
    spdlog::info("Demarrage de la boucle principale (Ctrl+C pour quitter)");

    using Clock = std::chrono::high_resolution_clock;
    auto startTime = Clock::now();
    auto lastFrame = Clock::now();
    const auto targetFrameTime = std::chrono::milliseconds(100); // 10 Hz

    grove::JsonDataNode frameInput("frame");
    uint64_t frameCount = 0;

    while (g_running) {
        auto frameStart = Clock::now();

        // Calculate times
        auto deltaTime = std::chrono::duration<float>(frameStart - lastFrame).count();
        auto gameTime = std::chrono::duration<float>(frameStart - startTime).count();
        lastFrame = frameStart;

        // Prepare frame input
        frameInput.setDouble("deltaTime", deltaTime);
        frameInput.setInt("frameCount", frameCount);
        frameInput.setDouble("gameTime", gameTime);

        // =====================================================================
        // Hot-Reload Check (every 10 frames = ~1 second)
        // =====================================================================
        if (frameCount % 10 == 0) {
            for (auto& [name, entry] : modules) {
                if (watcher.hasChanged(entry.path)) {
                    spdlog::info("Modification detectee: {}, hot-reload...", name);

                    // Get state before reload
                    std::unique_ptr<grove::IDataNode> state;
                    if (entry.module) {
                        state = entry.module->getState();
                    }

                    // Reload
                    auto reloaded = entry.loader->load(entry.path, name, true);
                    if (reloaded) {
                        auto config = loadConfig(configDir + entry.configFile);
                        reloaded->setConfiguration(*config, entry.io.get(), nullptr);

                        if (state) {
                            reloaded->setState(*state);
                        }

                        entry.module = reloaded.release();
                        spdlog::info("{} recharge avec succes!", name);
                    }
                }
            }
        }

        // =====================================================================
        // Process Services (non hot-reloadable infrastructure)
        // =====================================================================
        llmService.process();
        storageService.process();
        platformService.process();
        voiceService.process();

        // =====================================================================
        // Process Modules (hot-reloadable)
        // =====================================================================
        for (auto& [name, entry] : modules) {
            if (entry.module) {
                entry.module->process(frameInput);
            }
        }

        // =====================================================================
        // Route messages between modules and services
        // =====================================================================
        router.routeMessages();

        // =====================================================================
        // Process interactive mode responses
        // =====================================================================
        if (g_interactive && interactiveIO) {
            while (interactiveIO->hasMessages() > 0) {
                auto msg = interactiveIO->pullMessage();
                if (msg.topic == "llm:response" && msg.data) {
                    std::string text = msg.data->getString("text", "");
                    if (!text.empty()) {
                        std::cout << "\n[AISSIA] " << text << "\n> " << std::flush;
                    }
                } else if (msg.topic == "llm:error" && msg.data) {
                    std::string error = msg.data->getString("message", "Unknown error");
                    std::cout << "\n[ERREUR] " << error << "\n> " << std::flush;
                }
            }
        }

        // =====================================================================
        // Frame timing
        // =====================================================================
        frameCount++;

        auto frameEnd = Clock::now();
        auto frameDuration = frameEnd - frameStart;

        if (frameDuration < targetFrameTime) {
            std::this_thread::sleep_for(targetFrameTime - frameDuration);
        }

        // Status log every 30 seconds
        if (frameCount % 300 == 0) {
            int minutes = static_cast<int>(gameTime / 60.0f);
            int seconds = static_cast<int>(gameTime) % 60;
            spdlog::debug("Session: {}m{}s, {} modules actifs, 4 services",
                         minutes, seconds, modules.size());
        }
    }

    // =========================================================================
    // Shutdown
    // =========================================================================
    spdlog::info("Arret en cours...");

    // Stop interactive thread
    if (stdinThread && stdinThread->joinable()) {
        // Thread will exit when g_running becomes false or on EOF
        // We can't cleanly interrupt stdin reading, so just detach
        stdinThread->detach();
    }

    // Shutdown modules first
    for (auto& [name, entry] : modules) {
        if (entry.module) {
            entry.module->shutdown();
            spdlog::info("{} arrete", name);
        }
    }

    // Shutdown services
    voiceService.shutdown();
    platformService.shutdown();
    storageService.shutdown();
    llmService.shutdown();

    g_interactiveIO = nullptr;

    spdlog::info("A bientot!");
    return 0;
}
