// Step 249 TDD Test: MCP Server Integration Tests
//
// End-to-end integration tests exercising the full MCP server stack through
// MCPBridge. Simulates a realistic agent session: handshake, tool discovery,
// AST queries, pipeline execution, diagnostics, file operations, and compact
// AST comparison.
#include "HeadlessEditorState.h"
#include "MCPBridge.h"
#include "ast/Serialization.h"

#include <iostream>
#include <cassert>
#include <string>
#include <filesystem>
#include <fstream>

namespace fs = std::filesystem;

static void expect(bool cond, const std::string& name,
                   int& passed, int& failed) {
    if (cond) {
        std::cout << "Test " << (passed + failed + 1)
                  << " PASS: " << name << "\n";
        ++passed;
    } else {
        std::cout << "Test " << (passed + failed + 1)
                  << " FAIL: " << name << "\n";
        ++failed;
    }
}

// Resource reader (same wiring as mcp_main.cpp)
static json readResource(HeadlessEditorState& state,
                          const std::string& uri) {
    if (uri == "whetstone://ast") {
        Module* ast = state.activeAST();
        if (ast) return toJson(ast);
        return {{"error", "No active AST"}};
    }
    if (uri == "whetstone://diagnostics") {
        return state.buildDiagnosticsJson();
    }
    if (uri == "whetstone://libraries") {
        state.library.primitives.setRoot(state.activeAST());
        state.library.primitives.setLanguage(state.defaultLanguage);
        auto funcs = state.library.primitives.getAvailableFunctions();
        json libs = json::array();
        for (const auto& p : funcs) {
            libs.push_back(json{{"name", p.name}, {"kind", p.kind},
                                {"source", p.source}});
        }
        return libs;
    }
    if (uri == "whetstone://annotations") {
        Module* ast = state.activeAST();
        if (!ast) return json::array();
        json annotations = json::array();
        for (auto* child : ast->allChildren()) {
            if (isAnnotationNode(child)) {
                annotations.push_back(toJson(child));
            }
        }
        return annotations;
    }
    if (uri == "whetstone://settings") {
        return {
            {"workspace", state.workspaceRoot},
            {"language", state.defaultLanguage},
            {"verbose", state.verbose},
            {"bufferCount", (int)state.bufferStates.size()},
            {"mode", "headless-mcp"}
        };
    }
    return {{"error", "Unknown resource: " + uri}};
}

int main() {
    int passed = 0;
    int failed = 0;

    // --- Set up temp workspace ---
    std::string tmpDir = "/tmp/whetstone_test_249_" +
        std::to_string(std::chrono::steady_clock::now()
            .time_since_epoch().count());
    fs::create_directories(tmpDir);

    // --- Wire HeadlessEditorState + MCPBridge (same as mcp_main) ---
    HeadlessEditorState state;
    state.workspaceRoot = tmpDir;
    state.defaultLanguage = "python";
    state.verbose = false;

    // Open a buffer with multi-function Python source for AST tests
    std::string src;
    for (int i = 0; i < 10; ++i) {
        src += "def func_" + std::to_string(i) + "(x, y):\n";
        src += "    result = x + y\n";
        src += "    return result\n\n";
    }
    state.openBuffer("module.py", src, "python");
    state.setAgentRole("mcp-session", AgentRole::Refactor);

    MCPBridge bridge;
    bridge.setRequestHandler([&state](const json& request) -> json {
        return state.processAgentRequest(request, "mcp-session");
    });
    bridge.setResourceReader([&state](const std::string& uri) -> json {
        return readResource(state, uri);
    });

    // Helper: send MCP request, get parsed response
    auto mcp = [&](const std::string& method,
                    json params = json::object()) -> json {
        json req = {{"jsonrpc", "2.0"}, {"id", 1},
                    {"method", method}, {"params", params}};
        std::string resp = bridge.processMessage(req.dump());
        if (resp.empty()) return json(nullptr);
        return json::parse(resp);
    };

    // =================================================================
    //  Test 1: MCP handshake (initialize + notifications/initialized)
    // =================================================================
    {
        json initResp = mcp("initialize", {
            {"protocolVersion", "2024-11-05"},
            {"clientInfo", {{"name", "step249-integration-test"},
                            {"version", "1.0.0"}}}});
        bool hasProtocol = initResp.contains("result") &&
            initResp["result"].value("protocolVersion", "") == "2024-11-05";
        bool hasServer = initResp.contains("result") &&
            initResp["result"].contains("serverInfo") &&
            initResp["result"]["serverInfo"].value("name", "") ==
                "whetstone-mcp";
        bool hasCaps = initResp.contains("result") &&
            initResp["result"].contains("capabilities") &&
            initResp["result"]["capabilities"].contains("tools") &&
            initResp["result"]["capabilities"].contains("resources") &&
            initResp["result"]["capabilities"].contains("prompts");

        // Send notifications/initialized (no response expected)
        json notifResp = mcp("notifications/initialized");
        bool notifOk = notifResp.is_null();

        expect(hasProtocol && hasServer && hasCaps && notifOk,
               "MCP handshake: initialize + notifications/initialized",
               passed, failed);
    }

    // =================================================================
    //  Test 2: tools/list returns all 17 tools with valid schemas
    // =================================================================
    {
        json resp = mcp("tools/list");
        int toolCount = 0;
        bool allValid = true;
        if (resp.contains("result") && resp["result"].contains("tools")) {
            toolCount = (int)resp["result"]["tools"].size();
            for (const auto& tool : resp["result"]["tools"]) {
                if (!tool.contains("name") ||
                    !tool.contains("description") ||
                    !tool.contains("inputSchema")) {
                    allValid = false;
                }
                // Schema must have "type" field
                if (tool.contains("inputSchema") &&
                    !tool["inputSchema"].contains("type")) {
                    allValid = false;
                }
            }
        }
        expect(toolCount >= 17 && allValid,
               "tools/list returns 17+ tools with valid schemas (got " +
               std::to_string(toolCount) + ")",
               passed, failed);
    }

    // =================================================================
    //  Test 3: tools/call whetstone_get_ast returns valid AST
    // =================================================================
    {
        json resp = mcp("tools/call", {
            {"name", "whetstone_get_ast"},
            {"arguments", json::object()}});
        bool hasContent = resp.contains("result") &&
            resp["result"].contains("content") &&
            !resp["result"]["content"].empty();
        bool notError = resp.contains("result") &&
            resp["result"].value("isError", true) == false;
        std::string text;
        if (hasContent)
            text = resp["result"]["content"][0].value("text", "");
        // Should contain ast field and Module type
        bool hasAst = text.find("\"ast\"") != std::string::npos ||
                      text.find("\"type\"") != std::string::npos;
        expect(hasContent && notError && hasAst,
               "tools/call whetstone_get_ast returns valid AST data",
               passed, failed);
    }

    // =================================================================
    //  Test 4: tools/call whetstone_run_pipeline generates code
    // =================================================================
    {
        json resp = mcp("tools/call", {
            {"name", "whetstone_run_pipeline"},
            {"arguments", {
                {"source", "def add(a, b):\n    return a + b\n"},
                {"sourceLanguage", "python"},
                {"targetLanguage", "cpp"}}}});
        bool hasContent = resp.contains("result") &&
            resp["result"].contains("content") &&
            !resp["result"]["content"].empty();
        bool notError = resp.contains("result") &&
            resp["result"].value("isError", true) == false;
        std::string text;
        if (hasContent)
            text = resp["result"]["content"][0].value("text", "");
        // Generated C++ should contain function-like output
        bool hasCode = !text.empty() && text.size() > 10;
        expect(hasContent && notError && hasCode,
               "tools/call whetstone_run_pipeline generates code (" +
               std::to_string(text.size()) + " chars)",
               passed, failed);
    }

    // =================================================================
    //  Test 5: resources/read whetstone://diagnostics returns array
    // =================================================================
    {
        json resp = mcp("resources/read",
                         {{"uri", "whetstone://diagnostics"}});
        bool hasContents = resp.contains("result") &&
            resp["result"].contains("contents") &&
            !resp["result"]["contents"].empty();
        std::string text;
        if (hasContents)
            text = resp["result"]["contents"][0].value("text", "");
        // Diagnostics should be a JSON array (possibly empty)
        bool isArray = false;
        if (!text.empty()) {
            try {
                json diags = json::parse(text);
                isArray = diags.is_array();
            } catch (...) {}
        }
        expect(hasContents && isArray,
               "resources/read whetstone://diagnostics returns JSON array",
               passed, failed);
    }

    // =================================================================
    //  Test 6: prompts/list returns all 4 prompts with structure
    // =================================================================
    {
        json resp = mcp("prompts/list");
        int promptCount = 0;
        bool allValid = true;
        if (resp.contains("result") && resp["result"].contains("prompts")) {
            promptCount = (int)resp["result"]["prompts"].size();
            for (const auto& p : resp["result"]["prompts"]) {
                if (!p.contains("name") || !p.contains("description")) {
                    allValid = false;
                }
            }
        }
        // Verify known prompt names exist
        bool hasAnnotate = false, hasCross = false,
             hasSecurity = false, hasRefactor = false;
        if (resp.contains("result") && resp["result"].contains("prompts")) {
            for (const auto& p : resp["result"]["prompts"]) {
                std::string name = p.value("name", "");
                if (name == "annotate_module") hasAnnotate = true;
                if (name == "cross_language_projection") hasCross = true;
                if (name == "security_audit") hasSecurity = true;
                if (name == "refactor_memory") hasRefactor = true;
            }
        }
        expect(promptCount == 4 && allValid &&
               hasAnnotate && hasCross && hasSecurity && hasRefactor,
               "prompts/list returns 4 prompts with correct names",
               passed, failed);
    }

    // =================================================================
    //  Test 7: File operations cycle (create → write → read → diff)
    // =================================================================
    {
        // 7a: Create file via MCP tools/call
        json createResp = mcp("tools/call", {
            {"name", "whetstone_file_create"},
            {"arguments", {{"path", "agent_test.py"},
                           {"language", "python"},
                           {"template", "module"}}}});
        bool createOk = createResp.contains("result") &&
            createResp["result"].value("isError", true) == false;

        // 7b: Write content via MCP
        std::string newContent =
            "def compute(x):\n    return x * 2\n";
        json writeResp = mcp("tools/call", {
            {"name", "whetstone_file_write"},
            {"arguments", {{"path", "agent_test.py"},
                           {"content", newContent}}}});
        bool writeOk = writeResp.contains("result") &&
            writeResp["result"].value("isError", true) == false;

        // 7c: Read it back via MCP
        json readResp = mcp("tools/call", {
            {"name", "whetstone_file_read"},
            {"arguments", {{"path", "agent_test.py"}}}});
        bool readOk = readResp.contains("result") &&
            readResp["result"].value("isError", true) == false;
        std::string readText;
        if (readOk)
            readText = readResp["result"]["content"][0].value("text", "");
        bool contentMatch = readText.find("compute") != std::string::npos;

        // 7d: Verify file exists on disk
        bool onDisk = fs::exists(tmpDir + "/agent_test.py");

        expect(createOk && writeOk && readOk && contentMatch && onDisk,
               "File ops cycle: create → write → read → verify on disk",
               passed, failed);
    }

    // =================================================================
    //  Test 8: Compact AST vs full AST size comparison via MCP
    // =================================================================
    {
        // Full AST through MCP
        json fullResp = mcp("tools/call", {
            {"name", "whetstone_get_ast"},
            {"arguments", json::object()}});
        std::string fullText;
        if (fullResp.contains("result") &&
            fullResp["result"].contains("content") &&
            !fullResp["result"]["content"].empty()) {
            fullText = fullResp["result"]["content"][0].value("text", "");
        }

        // Compact AST through MCP
        json compactResp = mcp("tools/call", {
            {"name", "whetstone_get_ast"},
            {"arguments", {{"compact", true}}}});
        std::string compactText;
        if (compactResp.contains("result") &&
            compactResp["result"].contains("content") &&
            !compactResp["result"]["content"].empty()) {
            compactText =
                compactResp["result"]["content"][0].value("text", "");
        }

        bool bothNonEmpty = !fullText.empty() && !compactText.empty();
        double ratio = 1.0;
        if (bothNonEmpty)
            ratio = (double)compactText.size() / (double)fullText.size();
        expect(bothNonEmpty && ratio < 0.50,
               "Compact AST < 50% of full AST via MCP (" +
               std::to_string((int)(ratio * 100)) + "%, full=" +
               std::to_string(fullText.size()) + " compact=" +
               std::to_string(compactText.size()) + ")",
               passed, failed);
    }

    // --- Cleanup ---
    {
        std::error_code ec;
        fs::remove_all(tmpDir, ec);
    }

    std::cout << "\n=== Step 249 Results: " << passed << " passed, "
              << failed << " failed ===\n";
    return failed == 0 ? 0 : 1;
}
