// Step 17: JSON-RPC server - Ping and getAST methods.
//
// Orchestrator listens on stdin/stdout for JSON-RPC.
// Responds to `ping` and `getAST` methods.
// Test: send JSON-RPC from Python script, get response.

#include "Orchestrator.h"
#include <iostream>
#include <fstream>
#include <signal.h>
#include <cstdlib>
#include <thread>
#include <chrono>
#include <string>
#include <sstream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

// Forward declarations for helper functions
ASTNode* findNodeById(ASTNode* root, const std::string& id);
bool setNodeProperty(ASTNode* node, const std::string& property, const json& value);
void setNodeProperties(ASTNode* node, const json& properties);
ASTNode* createASTNode(const std::string& conceptType);
bool isRoleValid(ASTNode* parent, const std::string& role, ASTNode* child);
std::string generateUniqueId();

// Global orchestrator instance
std::unique_ptr<Orchestrator> g_orchestrator;

// Signal handler to save on termination
void signalHandler(int signal) {
    std::cout << "Received signal " << signal << ", saving and exiting..." << std::endl;
    if (g_orchestrator) {
        std::string filePath = g_orchestrator->getLoadedFilePath();
        if (!filePath.empty()) {
            g_orchestrator->saveAST(filePath);
            std::cout << "Saved AST back to: " << filePath << std::endl;
        }
    }
    exit(0);
}

// Process a JSON-RPC request
json processRequest(const json& request) {
    json response = json::object();
    response["jsonrpc"] = "2.0";
    response["id"] = request.contains("id") ? request["id"] : json(nullptr);
    
    try {
        std::string method = request.at("method");
        
        if (method == "ping") {
            response["result"] = "pong";
        }
        else if (method == "getAST") {
            if (g_orchestrator && g_orchestrator->getAST()) {
                // Serialize the AST to JSON
                json astJson = toJson(g_orchestrator->getAST());
                response["result"] = astJson;
            } else {
                response["error"] = json::object({
                    {"code", -32603},
                    {"message", "No AST loaded"}
                });
            }
        }
        else if (method == "setNodeProperty") {
            try {
                std::string nodeId = request.at("params").at("nodeId");
                std::string property = request.at("params").at("property");
                json value = request.at("params").at("value");
                
                // Find the node by ID in the AST
                ASTNode* node = findNodeById(g_orchestrator->getAST(), nodeId);
                if (!node) {
                    response["error"] = json::object({
                        {"code", -32602},
                        {"message", "Node not found: " + nodeId}
                    });
                } else {
                    // Set the property on the node
                    bool success = setNodeProperty(node, property, value);
                    if (success) {
                        response["result"] = true;
                    } else {
                        response["error"] = json::object({
                            {"code", -32603},
                            {"message", "Cannot set property '" + property + "' on node type '" + node->conceptType + "'"}
                        });
                    }
                }
            } catch (...) {
                response["error"] = json::object({
                    {"code", -32600},
                    {"message", "Invalid parameters for setNodeProperty"}
                });
            }
        }
        else if (method == "insertNode") {
            try {
                std::string parentId = request.at("params").at("parentId");
                std::string role = request.at("params").at("role");
                std::string conceptType = request.at("params").at("concept");
                json properties = request.at("params").at("properties");
                
                // Find the parent node by ID in the AST
                ASTNode* parentNode = findNodeById(g_orchestrator->getAST(), parentId);
                if (!parentNode) {
                    response["error"] = json::object({
                        {"code", -32602},
                        {"message", "Parent node not found: " + parentId}
                    });
                } else {
                    // Check if the parent node or any of its ancestors is locked
                    if (g_orchestrator->isNodeLocked(parentNode)) {
                        const OptimizationLock* lock = g_orchestrator->getNodeLock(parentNode);
                        std::string lockOwner = lock ? lock->lockedBy : "unknown";
                        response["error"] = json::object({
                            {"code", -32604},
                            {"message", "Cannot insert node: parent node is locked by " + lockOwner + 
                                       ". Insertion blocked for optimization protection."}
                        });
                    } else {
                        // Create and insert the new node
                        ASTNode* newNode = createASTNode(conceptType);
                        if (!newNode) {
                            response["error"] = json::object({
                                {"code", -32603},
                                {"message", "Unknown concept: " + conceptType}
                            });
                        } else {
                            // Set the node ID and properties
                            newNode->id = generateUniqueId(); // Generate a unique ID
                            setNodeProperties(newNode, properties);
                            
                            // Add to parent in the specified role
                            if (isRoleValid(parentNode, role, newNode)) {
                                parentNode->addChild(role, newNode);
                                
                                // Record the operation in the journal for undo/redo
                                json operation = json::object({
                                    {"type", "insertNode"},
                                    {"nodeId", newNode->id},
                                    {"parentId", parentId},
                                    {"role", role},
                                    {"concept", conceptType},
                                    {"properties", properties}
                                });
                                g_orchestrator->recordOperation(operation);
                                
                                response["result"] = toJson(newNode); // Return the inserted node as JSON
                            } else {
                                delete newNode; // Clean up the node if insertion failed
                                response["error"] = json::object({
                                    {"code", -32603},
                                    {"message", "Invalid role '" + role + "' for concept '" + parentNode->conceptType + 
                                               "' with child concept '" + newNode->conceptType + "'"}
                                });
                            }
                        }
                    }
                }
            } catch (...) {
                response["error"] = json::object({
                    {"code", -32600},
                    {"message", "Invalid parameters for insertNode"}
                });
            }
        }
        else if (method == "setNodeProperty") {
            try {
                std::string nodeId = request.at("params").at("nodeId");
                std::string property = request.at("params").at("property");
                json value = request.at("params").at("value");
                
                // Get the node by ID in the AST
                ASTNode* node = findNodeById(g_orchestrator->getAST(), nodeId);
                if (!node) {
                    response["error"] = json::object({
                        {"code", -32602},
                        {"message", "Node not found: " + nodeId}
                    });
                } else {
                    // Check if the node or any of its ancestors is locked
                    if (g_orchestrator->isNodeLocked(node)) {
                        const OptimizationLock* lock = g_orchestrator->getNodeLock(node);
                        std::string lockOwner = lock ? lock->lockedBy : "unknown";
                        response["error"] = json::object({
                            {"code", -32604},
                            {"message", "Cannot modify property '" + property + "': node is locked by " + lockOwner + 
                                       ". Modification blocked for optimization protection."}
                        });
                    } else {
                        // Store old value for undo
                        json oldValue;
                        if (node->conceptType == "Function" && property == "name") {
                            oldValue = static_cast<Function*>(node)->name;
                        } else if (node->conceptType == "Variable" && property == "name") {
                            oldValue = static_cast<Variable*>(node)->name;
                        } else if (node->conceptType == "Parameter" && property == "name") {
                            oldValue = static_cast<Parameter*>(node)->name;
                        } else {
                            response["error"] = json::object({
                                {"code", -32603},
                                {"message", "Cannot set property '" + property + "' on node type '" + node->conceptType + "'"}
                            });
                            return response;
                        }
                        
                        // Set the property on the node
                        bool success = setNodeProperty(node, property, value);
                        if (success) {
                            // Record the operation in the journal for undo/redo
                            json operation = json::object({
                                {"type", "setNodeProperty"},
                                {"nodeId", nodeId},
                                {"property", property},
                                {"oldValue", oldValue},
                                {"newValue", value}
                            });
                            g_orchestrator->recordOperation(operation);
                            
                            response["result"] = true;
                        } else {
                            response["error"] = json::object({
                                {"code", -32603},
                                {"message", "Cannot set property '" + property + "' on node type '" + node->conceptType + "'"}
                            });
                        }
                    }
                }
            } catch (...) {
                response["error"] = json::object({
                    {"code", -32600},
                    {"message", "Invalid parameters for setNodeProperty"}
                });
            }
        }
        else if (method == "undo") {
            try {
                bool success = g_orchestrator->undoLastOperation();
                if (success) {
                    response["result"] = true;
                } else {
                    response["error"] = json::object({
                        {"code", -32603},
                        {"message", "No operations to undo"}
                    });
                }
            } catch (...) {
                response["error"] = json::object({
                    {"code", -32600},
                    {"message", "Error during undo operation"}
                });
            }
        }
        else if (method == "redo") {
            try {
                bool success = g_orchestrator->redoLastOperation();
                if (success) {
                    response["result"] = true;
                } else {
                    response["error"] = json::object({
                        {"code", -32603},
                        {"message", "No operations to redo"}
                    });
                }
            } catch (...) {
                response["error"] = json::object({
                    {"code", -32600},
                    {"message", "Error during redo operation"}
                });
            }
        }
        else if (method == "sendToEmacs") {
            try {
                std::string command = request.at("params").at("command");
                std::string result = g_orchestrator->sendToEmacs(command);
                response["result"] = result;
            } catch (...) {
                response["error"] = json::object({
                    {"code", -32600},
                    {"message", "Invalid parameters for sendToEmacs"}
                });
            }
        }
        else if (method == "loadFile") {
            try {
                std::string path = request.at("params").at("path");
                auto module = g_orchestrator->loadFile(path);
                if (module) {
                    PythonGenerator gen;
                    response["result"] = gen.generate(module.get());
                } else {
                    response["result"] = "";
                }
            } catch (...) {
                response["error"] = json::object({
                    {"code", -32600},
                    {"message", "Invalid parameters for loadFile"}
                });
            }
        }
        else if (method == "saveFile") {
            try {
                std::string path = request.at("params").at("path");
                std::string content = request.at("params").at("content");
                bool success = g_orchestrator->saveContent(path, content);
                if (success) {
                    response["result"] = true;
                } else {
                    response["error"] = json::object({
                        {"code", -32603},
                        {"message", "Failed to save file: " + path}
                    });
                }
            } catch (...) {
                response["error"] = json::object({
                    {"code", -32600},
                    {"message", "Invalid parameters for saveFile"}
                });
            }
        }
        else if (method == "getLocks") {
            try {
                std::string nodeId = request.at("params").at("nodeId");
                std::vector<const OptimizationLock*> locks = g_orchestrator->getLocks(nodeId);
                
                // Convert the locks to JSON
                json locksJson = json::array();
                for (const auto* lock : locks) {
                    json lockJson = json::object();
                    lockJson["lockedBy"] = lock->lockedBy;
                    lockJson["lockReason"] = lock->lockReason;
                    lockJson["lockLevel"] = lock->lockLevel;
                    if (!lock->affectedStrategies.empty()) {
                        lockJson["affectedStrategies"] = lock->affectedStrategies;
                    }
                    if (!lock->timestamp.empty()) {
                        lockJson["timestamp"] = lock->timestamp;
                    }
                    locksJson.push_back(lockJson);
                }
                
                response["result"] = locksJson;
            } catch (...) {
                response["error"] = json::object({
                    {"code", -32600},
                    {"message", "Invalid parameters for getLocks"}
                });
            }
        }
        else {
            response["error"] = json::object({
                {"code", -32601},
                {"message", "Method not found: " + method}
            });
        }
    } catch (const std::exception& e) {
        response["error"] = json::object({
            {"code", -32600},
            {"message", "Invalid Request: " + std::string(e.what())}
        });
    } catch (...) {
        response["error"] = json::object({
            {"code", -32600},
            {"message", "Invalid Request"}
        });
    }
    
    return response;
}

// Helper function to find a node by ID in the AST
ASTNode* findNodeById(ASTNode* root, const std::string& id) {
    if (!root) return nullptr;
    if (root->id == id) return root;
    
    // Recursively search in children
    for (auto* child : root->allChildren()) {
        ASTNode* found = findNodeById(child, id);
        if (found) return found;
    }
    
    return nullptr;
}

// Helper function to set a property on a node
bool setNodeProperty(ASTNode* node, const std::string& property, const json& value) {
    // This is a simplified implementation - in a real system, we'd have specific setters for each concept
    // For now, we'll handle a few common properties
    if (node->conceptType == "Function" && property == "name") {
        if (value.is_string()) {
            static_cast<Function*>(node)->name = value.get<std::string>();
            return true;
        }
    } else if (node->conceptType == "Variable" && property == "name") {
        if (value.is_string()) {
            static_cast<Variable*>(node)->name = value.get<std::string>();
            return true;
        }
    } else if (node->conceptType == "Parameter" && property == "name") {
        if (value.is_string()) {
            static_cast<Parameter*>(node)->name = value.get<std::string>();
            return true;
        }
    }
    // Add more property setters as needed
    
    return false; // Property not supported
}

// Helper function to set multiple properties on a node
void setNodeProperties(ASTNode* node, const json& properties) {
    for (auto& [propName, propValue] : properties.items()) {
        setNodeProperty(node, propName, propValue);
    }
}

// Helper function to set a single property on a node (separate implementation to avoid recursion)
bool setNodePropertyDirect(ASTNode* node, const std::string& property, const json& value) {
    // This is a simplified implementation - in a real system, we'd have specific setters for each concept
    // For now, we'll handle a few common properties
    if (node->conceptType == "Function" && property == "name") {
        if (value.is_string()) {
            static_cast<Function*>(node)->name = value.get<std::string>();
            return true;
        }
    } else if (node->conceptType == "Variable" && property == "name") {
        if (value.is_string()) {
            static_cast<Variable*>(node)->name = value.get<std::string>();
            return true;
        }
    } else if (node->conceptType == "Parameter" && property == "name") {
        if (value.is_string()) {
            static_cast<Parameter*>(node)->name = value.get<std::string>();
            return true;
        }
    }
    // Add more property setters as needed
    
    return false; // Property not supported
}

// Helper function to create a new node of the specified concept
ASTNode* createASTNode(const std::string& conceptType) {
    if (conceptType == "Function") return new Function();
    if (conceptType == "Variable") return new Variable();
    if (conceptType == "Parameter") return new Parameter();
    if (conceptType == "Assignment") return new Assignment();
    if (conceptType == "Return") return new Return();
    if (conceptType == "BinaryOperation") return new BinaryOperation();
    if (conceptType == "VariableReference") return new VariableReference();
    if (conceptType == "IntegerLiteral") return new IntegerLiteral();
    if (conceptType == "StringLiteral") return new StringLiteral();
    if (conceptType == "BooleanLiteral") return new BooleanLiteral();
    if (conceptType == "PrimitiveType") return new PrimitiveType();
    // Add more concepts as needed
    
    return nullptr;
}

// Helper function to check if a role is valid for a parent-child relationship
bool isRoleValid(ASTNode* parent, const std::string& role, ASTNode* child) {
    // This is a simplified implementation - in a real system, we'd have a schema to check validity
    // For now, we'll allow common roles based on parent concept
    if (parent->conceptType == "Module") {
        return (role == "functions" || role == "variables" || role == "annotations");
    } else if (parent->conceptType == "Function") {
        return (role == "parameters" || role == "body" || role == "annotations" || role == "returnType");
    } else if (parent->conceptType == "Assignment") {
        return (role == "target" || role == "value");
    } else if (parent->conceptType == "BinaryOperation") {
        return (role == "left" || role == "right");
    }
    // Add more role validations as needed
    
    return false; // Role not supported
}

// Helper function to generate a unique ID
std::string generateUniqueId() {
    static int counter = 0;
    return "RPC_" + std::to_string(++counter);
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " <path_to_json_file>" << std::endl;
        return 1;
    }
    
    std::string filePath = argv[1];
    
    // Set up signal handlers to save on termination
    signal(SIGINT, signalHandler);
    signal(SIGTERM, signalHandler);
    
    // Create orchestrator
    g_orchestrator = std::make_unique<Orchestrator>();
    
    // Start the Emacs daemon
    g_orchestrator->startEmacsDaemon();
    
    // Load the AST from the specified file
    if (!g_orchestrator->loadAST(filePath)) {
        std::cerr << "Failed to load AST from: " << filePath << std::endl;
        return 1;
    }
    
    std::cout << "Loaded AST from: " << filePath << std::endl;
    std::cout << "Orchestrator running with JSON-RPC server..." << std::endl;
    std::cout << "Listening for JSON-RPC requests on stdin..." << std::endl;
    
    // Main loop to handle JSON-RPC requests from stdin
    std::string line;
    while (std::getline(std::cin, line)) {
        try {
            // Parse the incoming JSON-RPC request
            json request = json::parse(line);
            
            // Process the request
            json response = processRequest(request);
            
            // Send the response to stdout
            std::cout << response.dump() << std::endl;
            std::cout.flush();
        } catch (const std::exception& e) {
            // Send error response if request parsing fails
            json errorResponse = json::object({
                {"jsonrpc", "2.0"},
                {"id", json(nullptr)}, // Use null if we couldn't parse the original id
                {"error", json::object({
                    {"code", -32700},
                    {"message", "Parse error: " + std::string(e.what())}
                })}
            });
            std::cout << errorResponse.dump() << std::endl;
            std::cout.flush();
        }
    }
    
    return 0;
}