/**
 * @file MCPClientTests.cpp
 * @brief Integration tests for MCPClient (15 TI)
 */

#include <catch2/catch_test_macros.hpp>
#include "shared/mcp/MCPClient.hpp"
#include "mocks/MockTransport.hpp"

#include <fstream>
#include <filesystem>

using namespace celuna::mcp;
using namespace celuna::tests;
using json = nlohmann::json;

// ============================================================================
// Helper: Create test config file
// ============================================================================

std::string createTestConfigFile(const json& config) {
    std::string path = "test_mcp_config.json";
    std::ofstream file(path);
    file << config.dump(2);
    file.close();
    return path;
}

void cleanupTestConfigFile(const std::string& path) {
    std::filesystem::remove(path);
}

// ============================================================================
// TI_CLIENT_001: Load config valid
// ============================================================================

TEST_CASE("TI_CLIENT_001_LoadConfigValid", "[mcp][client]") {
    json config = {
        {"servers", {
            {"test_server", {
                {"command", "C:\\Users\\alexi\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"},
                {"args", json::array({"server.py"})},
                {"enabled", true}
            }}
        }}
    };
    auto path = createTestConfigFile(config);

    MCPClient client;
    bool loaded = client.loadConfig(path);

    REQUIRE(loaded == true);

    cleanupTestConfigFile(path);
}

// ============================================================================
// TI_CLIENT_002: Load config invalid
// ============================================================================

TEST_CASE("TI_CLIENT_002_LoadConfigInvalid", "[mcp][client]") {
    // Create file with invalid JSON
    std::string path = "invalid_config.json";
    std::ofstream file(path);
    file << "{ invalid json }";
    file.close();

    MCPClient client;
    bool loaded = client.loadConfig(path);

    REQUIRE(loaded == false);

    cleanupTestConfigFile(path);
}

// ============================================================================
// TI_CLIENT_003: Load config missing file
// ============================================================================

TEST_CASE("TI_CLIENT_003_LoadConfigMissingFile", "[mcp][client]") {
    MCPClient client;
    bool loaded = client.loadConfig("nonexistent_file.json");

    REQUIRE(loaded == false);
}

// ============================================================================
// TI_CLIENT_004: ConnectAll starts servers
// ============================================================================

TEST_CASE("TI_CLIENT_004_ConnectAllStartsServers", "[mcp][client]") {
    // Use the real mock MCP server fixture
    MCPClient client;
    bool loaded = client.loadConfig("tests/fixtures/mock_mcp.json");

    if (loaded) {
        int connected = client.connectAll();
        // Should connect to enabled servers
        REQUIRE(connected >= 0);
        client.disconnectAll();
    } else {
        // Skip if fixture not available
        SUCCEED();
    }
}

// ============================================================================
// TI_CLIENT_005: ConnectAll skips disabled
// ============================================================================

TEST_CASE("TI_CLIENT_005_ConnectAllSkipsDisabled", "[mcp][client]") {
    json config = {
        {"servers", {
            {"enabled_server", {
                {"command", "C:\\Users\\alexi\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"},
                {"args", json::array({"tests/fixtures/echo_server.py"})},
                {"enabled", true}
            }},
            {"disabled_server", {
                {"command", "nonexistent"},
                {"enabled", false}
            }}
        }}
    };
    auto path = createTestConfigFile(config);

    MCPClient client;
    client.loadConfig(path);
    int connected = client.connectAll();

    // disabled_server should not be connected
    REQUIRE(client.isConnected("disabled_server") == false);

    client.disconnectAll();
    cleanupTestConfigFile(path);
}

// ============================================================================
// TI_CLIENT_006: Connect single server
// ============================================================================

TEST_CASE("TI_CLIENT_006_ConnectSingleServer", "[mcp][client]") {
    json config = {
        {"servers", {
            {"server1", {
                {"command", "C:\\Users\\alexi\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"},
                {"args", json::array({"tests/fixtures/echo_server.py"})},
                {"enabled", true}
            }},
            {"server2", {
                {"command", "C:\\Users\\alexi\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"},
                {"args", json::array({"tests/fixtures/echo_server.py"})},
                {"enabled", true}
            }}
        }}
    };
    auto path = createTestConfigFile(config);

    MCPClient client;
    client.loadConfig(path);

    // Connect only server1
    bool connected = client.connect("server1");

    REQUIRE(connected == true);
    REQUIRE(client.isConnected("server1") == true);
    REQUIRE(client.isConnected("server2") == false);

    client.disconnectAll();
    cleanupTestConfigFile(path);
}

// ============================================================================
// TI_CLIENT_007: Disconnect single server
// ============================================================================

TEST_CASE("TI_CLIENT_007_DisconnectSingleServer", "[mcp][client]") {
    json config = {
        {"servers", {
            {"server1", {
                {"command", "C:\\Users\\alexi\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"},
                {"args", json::array({"tests/fixtures/echo_server.py"})},
                {"enabled", true}
            }}
        }}
    };
    auto path = createTestConfigFile(config);

    MCPClient client;
    client.loadConfig(path);
    client.connect("server1");
    REQUIRE(client.isConnected("server1") == true);

    client.disconnect("server1");
    REQUIRE(client.isConnected("server1") == false);

    cleanupTestConfigFile(path);
}

// ============================================================================
// TI_CLIENT_008: DisconnectAll cleans up
// ============================================================================

TEST_CASE("TI_CLIENT_008_DisconnectAllCleansUp", "[mcp][client]") {
    json config = {
        {"servers", {
            {"server1", {
                {"command", "C:\\Users\\alexi\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"},
                {"args", json::array({"tests/fixtures/echo_server.py"})},
                {"enabled", true}
            }},
            {"server2", {
                {"command", "C:\\Users\\alexi\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"},
                {"args", json::array({"tests/fixtures/echo_server.py"})},
                {"enabled", true}
            }}
        }}
    };
    auto path = createTestConfigFile(config);

    MCPClient client;
    client.loadConfig(path);
    client.connectAll();

    client.disconnectAll();

    REQUIRE(client.isConnected("server1") == false);
    REQUIRE(client.isConnected("server2") == false);
    REQUIRE(client.getConnectedServers().empty() == true);

    cleanupTestConfigFile(path);
}

// ============================================================================
// TI_CLIENT_009: ListAllTools aggregates
// ============================================================================

TEST_CASE("TI_CLIENT_009_ListAllToolsAggregates", "[mcp][client]") {
    MCPClient client;
    bool loaded = client.loadConfig("tests/fixtures/mock_mcp.json");

    if (loaded) {
        client.connectAll();
        auto tools = client.listAllTools();

        // Should have tools from mock server
        REQUIRE(tools.size() >= 0);

        client.disconnectAll();
    } else {
        SUCCEED();
    }
}

// ============================================================================
// TI_CLIENT_010: Tool name prefixed
// ============================================================================

TEST_CASE("TI_CLIENT_010_ToolNamePrefixed", "[mcp][client]") {
    MCPClient client;
    bool loaded = client.loadConfig("tests/fixtures/mock_mcp.json");

    if (loaded) {
        client.connectAll();
        auto tools = client.listAllTools();

        bool hasPrefix = false;
        for (const auto& tool : tools) {
            if (tool.name.find(":") != std::string::npos) {
                hasPrefix = true;
                break;
            }
        }

        if (!tools.empty()) {
            REQUIRE(hasPrefix == true);
        }

        client.disconnectAll();
    } else {
        SUCCEED();
    }
}

// ============================================================================
// TI_CLIENT_011: CallTool routes to server
// ============================================================================

TEST_CASE("TI_CLIENT_011_CallToolRoutesToServer", "[mcp][client]") {
    MCPClient client;
    bool loaded = client.loadConfig("tests/fixtures/mock_mcp.json");

    if (loaded) {
        client.connectAll();
        auto tools = client.listAllTools();

        if (!tools.empty()) {
            // Call the first available tool
            auto result = client.callTool(tools[0].name, json::object());
            // Should get some result (success or error)
            REQUIRE(result.content.size() >= 0);
        }

        client.disconnectAll();
    } else {
        SUCCEED();
    }
}

// ============================================================================
// TI_CLIENT_012: CallTool invalid name
// ============================================================================

TEST_CASE("TI_CLIENT_012_CallToolInvalidName", "[mcp][client]") {
    MCPClient client;
    client.loadConfig("tests/fixtures/mock_mcp.json");
    client.connectAll();

    auto result = client.callTool("nonexistent:tool", json::object());

    REQUIRE(result.isError == true);

    client.disconnectAll();
}

// ============================================================================
// TI_CLIENT_013: CallTool disconnected server
// ============================================================================

TEST_CASE("TI_CLIENT_013_CallToolDisconnectedServer", "[mcp][client]") {
    MCPClient client;
    // Don't connect any servers

    auto result = client.callTool("server:tool", json::object());

    REQUIRE(result.isError == true);
}

// ============================================================================
// TI_CLIENT_014: ToolCount accurate
// ============================================================================

TEST_CASE("TI_CLIENT_014_ToolCountAccurate", "[mcp][client]") {
    MCPClient client;
    bool loaded = client.loadConfig("tests/fixtures/mock_mcp.json");

    if (loaded) {
        client.connectAll();

        size_t count = client.toolCount();
        auto tools = client.listAllTools();

        REQUIRE(count == tools.size());

        client.disconnectAll();
    } else {
        SUCCEED();
    }
}

// ============================================================================
// TI_CLIENT_015: IsConnected accurate
// ============================================================================

TEST_CASE("TI_CLIENT_015_IsConnectedAccurate", "[mcp][client]") {
    json config = {
        {"servers", {
            {"test_server", {
                {"command", "C:\\Users\\alexi\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"},
                {"args", json::array({"tests/fixtures/echo_server.py"})},
                {"enabled", true}
            }}
        }}
    };
    auto path = createTestConfigFile(config);

    MCPClient client;
    client.loadConfig(path);

    // Not connected yet
    REQUIRE(client.isConnected("test_server") == false);

    // Connect
    client.connect("test_server");
    REQUIRE(client.isConnected("test_server") == true);

    // Disconnect
    client.disconnect("test_server");
    REQUIRE(client.isConnected("test_server") == false);

    cleanupTestConfigFile(path);
}
