/**
 * @file StdioTransportTests.cpp
 * @brief Integration tests for StdioTransport (20 TI)
 */

#include <catch2/catch_test_macros.hpp>
#include "shared/mcp/StdioTransport.hpp"
#include "shared/mcp/MCPTypes.hpp"

#include <thread>
#include <chrono>

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

// ============================================================================
// Helper: Create config for echo server
// ============================================================================

MCPServerConfig makeEchoServerConfig() {
    MCPServerConfig config;
    config.name = "echo";
    config.command = "C:\\Users\\alexi\\AppData\\Local\\Programs\\Python\\Python312\\python.exe";
    config.args = {"tests/fixtures/echo_server.py"};
    config.enabled = true;
    return config;
}

MCPServerConfig makeMockMCPServerConfig() {
    MCPServerConfig config;
    config.name = "mock_mcp";
    config.command = "C:\\Users\\alexi\\AppData\\Local\\Programs\\Python\\Python312\\python.exe";
    config.args = {"tests/fixtures/mock_mcp_server.py"};
    config.enabled = true;
    return config;
}

// ============================================================================
// TI_TRANSPORT_001: Start spawns process
// ============================================================================

TEST_CASE("TI_TRANSPORT_001_StartSpawnsProcess", "[mcp][transport]") {
    auto config = makeEchoServerConfig();
    StdioTransport transport(config);

    bool started = transport.start();

    REQUIRE(started == true);
    REQUIRE(transport.isRunning() == true);

    transport.stop();
}

// ============================================================================
// TI_TRANSPORT_002: Start fails with invalid command
// ============================================================================

TEST_CASE("TI_TRANSPORT_002_StartFailsInvalidCommand", "[mcp][transport]") {
    MCPServerConfig config;
    config.name = "invalid";
    config.command = "nonexistent_command_xyz";
    config.enabled = true;

    StdioTransport transport(config);

    bool started = transport.start();

    REQUIRE(started == false);
    REQUIRE(transport.isRunning() == false);
}

// ============================================================================
// TI_TRANSPORT_003: Stop kills process
// ============================================================================

TEST_CASE("TI_TRANSPORT_003_StopKillsProcess", "[mcp][transport]") {
    auto config = makeEchoServerConfig();
    StdioTransport transport(config);

    transport.start();
    REQUIRE(transport.isRunning() == true);

    transport.stop();
    REQUIRE(transport.isRunning() == false);
}

// ============================================================================
// TI_TRANSPORT_004: IsRunning reflects state
// ============================================================================

TEST_CASE("TI_TRANSPORT_004_IsRunningReflectsState", "[mcp][transport]") {
    auto config = makeEchoServerConfig();
    StdioTransport transport(config);

    REQUIRE(transport.isRunning() == false);

    transport.start();
    REQUIRE(transport.isRunning() == true);

    transport.stop();
    REQUIRE(transport.isRunning() == false);
}

// ============================================================================
// TI_TRANSPORT_005: SendRequest writes to stdin
// ============================================================================

TEST_CASE("TI_TRANSPORT_005_SendRequestWritesToStdin", "[mcp][transport]") {
    auto config = makeEchoServerConfig();
    StdioTransport transport(config);
    transport.start();

    JsonRpcRequest request;
    request.id = 1;
    request.method = "test";
    request.params = {{"message", "hello"}};

    // Echo server will echo back params as result
    auto response = transport.sendRequest(request, 5000);

    // If we got a response, the request was written
    REQUIRE(response.isError() == false);

    transport.stop();
}

// ============================================================================
// TI_TRANSPORT_006: SendRequest reads response
// ============================================================================

TEST_CASE("TI_TRANSPORT_006_SendRequestReadsResponse", "[mcp][transport]") {
    auto config = makeEchoServerConfig();
    StdioTransport transport(config);
    transport.start();

    JsonRpcRequest request;
    request.id = 42;
    request.method = "echo";
    request.params = {{"value", 123}};

    auto response = transport.sendRequest(request, 5000);

    REQUIRE(response.isError() == false);
    REQUIRE(response.id == 42);
    REQUIRE(response.result.has_value());
    REQUIRE(response.result.value()["value"] == 123);

    transport.stop();
}

// ============================================================================
// TI_TRANSPORT_007: SendRequest timeout
// ============================================================================

TEST_CASE("TI_TRANSPORT_007_SendRequestTimeout", "[mcp][transport]") {
    // Use cat which doesn't respond to JSON-RPC
    MCPServerConfig config;
    config.name = "cat";
    config.command = "cat";
    config.enabled = true;

    StdioTransport transport(config);
    transport.start();

    JsonRpcRequest request;
    request.id = 1;
    request.method = "test";

    // Very short timeout
    auto response = transport.sendRequest(request, 100);

    // Should timeout and return error
    REQUIRE(response.isError() == true);

    transport.stop();
}

// ============================================================================
// TI_TRANSPORT_008: SendRequest ID matching
// ============================================================================

TEST_CASE("TI_TRANSPORT_008_SendRequestIdMatching", "[mcp][transport]") {
    auto config = makeEchoServerConfig();
    StdioTransport transport(config);
    transport.start();

    // Send request with specific ID
    JsonRpcRequest request;
    request.id = 999;
    request.method = "test";

    auto response = transport.sendRequest(request, 5000);

    // Response ID should match request ID
    REQUIRE(response.id == 999);

    transport.stop();
}

// ============================================================================
// TI_TRANSPORT_009: Concurrent requests
// ============================================================================

TEST_CASE("TI_TRANSPORT_009_ConcurrentRequests", "[mcp][transport]") {
    auto config = makeEchoServerConfig();
    StdioTransport transport(config);
    transport.start();

    std::vector<std::thread> threads;
    std::vector<bool> results(5, false);

    for (int i = 0; i < 5; i++) {
        threads.emplace_back([&transport, &results, i]() {
            JsonRpcRequest request;
            request.id = 100 + i;
            request.method = "test";
            request.params = {{"index", i}};

            auto response = transport.sendRequest(request, 5000);
            results[i] = !response.isError() && response.id == 100 + i;
        });
    }

    for (auto& t : threads) {
        t.join();
    }

    // All requests should succeed
    for (bool result : results) {
        REQUIRE(result == true);
    }

    transport.stop();
}

// ============================================================================
// TI_TRANSPORT_010: SendNotification no response
// ============================================================================

TEST_CASE("TI_TRANSPORT_010_SendNotificationNoResponse", "[mcp][transport]") {
    auto config = makeEchoServerConfig();
    StdioTransport transport(config);
    transport.start();

    // Should not block or throw
    REQUIRE_NOTHROW(transport.sendNotification("notification/test", {{"data", "value"}}));

    transport.stop();
}

// ============================================================================
// TI_TRANSPORT_011: Reader thread starts on start
// ============================================================================

TEST_CASE("TI_TRANSPORT_011_ReaderThreadStartsOnStart", "[mcp][transport]") {
    auto config = makeEchoServerConfig();
    StdioTransport transport(config);

    transport.start();

    // If reader thread didn't start, sendRequest would hang
    JsonRpcRequest request;
    request.id = 1;
    request.method = "test";

    auto response = transport.sendRequest(request, 1000);

    // Got response means reader thread is working
    REQUIRE(response.isError() == false);

    transport.stop();
}

// ============================================================================
// TI_TRANSPORT_012: Reader thread stops on stop
// ============================================================================

TEST_CASE("TI_TRANSPORT_012_ReaderThreadStopsOnStop", "[mcp][transport]") {
    auto config = makeEchoServerConfig();
    StdioTransport transport(config);

    transport.start();
    transport.stop();

    // Should not hang or crash on destruction
    SUCCEED();
}

// ============================================================================
// TI_TRANSPORT_013: JSON parse error handled
// ============================================================================

TEST_CASE("TI_TRANSPORT_013_JsonParseErrorHandled", "[mcp][transport]") {
    auto config = makeEchoServerConfig();
    StdioTransport transport(config);
    transport.start();

    // Send valid request - server will respond with valid JSON
    JsonRpcRequest request;
    request.id = 1;
    request.method = "test";

    // Should not crash even if server sends invalid JSON
    REQUIRE_NOTHROW(transport.sendRequest(request, 1000));

    transport.stop();
}

// ============================================================================
// TI_TRANSPORT_014: Process crash detected
// ============================================================================

TEST_CASE("TI_TRANSPORT_014_ProcessCrashDetected", "[mcp][transport]") {
    // TODO: Need a server that crashes to test this
    // For now, just verify we can handle stop
    auto config = makeEchoServerConfig();
    StdioTransport transport(config);

    transport.start();
    transport.stop();

    REQUIRE(transport.isRunning() == false);
}

// ============================================================================
// TI_TRANSPORT_015: Large message handling
// ============================================================================

TEST_CASE("TI_TRANSPORT_015_LargeMessageHandling", "[mcp][transport]") {
    auto config = makeEchoServerConfig();
    StdioTransport transport(config);
    transport.start();

    // Create large params
    std::string largeString(10000, 'x');
    JsonRpcRequest request;
    request.id = 1;
    request.method = "test";
    request.params = {{"data", largeString}};

    auto response = transport.sendRequest(request, 10000);

    REQUIRE(response.isError() == false);
    REQUIRE(response.result.value()["data"] == largeString);

    transport.stop();
}

// ============================================================================
// TI_TRANSPORT_016: Multiline JSON handling
// ============================================================================

TEST_CASE("TI_TRANSPORT_016_MultilineJsonHandling", "[mcp][transport]") {
    auto config = makeEchoServerConfig();
    StdioTransport transport(config);
    transport.start();

    // JSON with newlines in strings should work
    JsonRpcRequest request;
    request.id = 1;
    request.method = "test";
    request.params = {{"text", "line1\nline2\nline3"}};

    auto response = transport.sendRequest(request, 5000);

    REQUIRE(response.isError() == false);

    transport.stop();
}

// ============================================================================
// TI_TRANSPORT_017: Env variables passed to process
// ============================================================================

TEST_CASE("TI_TRANSPORT_017_EnvVariablesPassedToProcess", "[mcp][transport]") {
    auto config = makeEchoServerConfig();
    config.env["TEST_VAR"] = "test_value";

    StdioTransport transport(config);
    bool started = transport.start();

    REQUIRE(started == true);

    transport.stop();
}

// ============================================================================
// TI_TRANSPORT_018: Args passed to process
// ============================================================================

TEST_CASE("TI_TRANSPORT_018_ArgsPassedToProcess", "[mcp][transport]") {
    auto config = makeMockMCPServerConfig();
    // Args are already set in the helper function

    StdioTransport transport(config);
    bool started = transport.start();

    REQUIRE(started == true);

    transport.stop();
}

// ============================================================================
// TI_TRANSPORT_019: Destructor cleans up
// ============================================================================

TEST_CASE("TI_TRANSPORT_019_DestructorCleansUp", "[mcp][transport]") {
    {
        auto config = makeEchoServerConfig();
        StdioTransport transport(config);
        transport.start();
        // Destructor called here
    }

    // Should not leak resources or hang
    SUCCEED();
}

// ============================================================================
// TI_TRANSPORT_020: Restart after stop
// ============================================================================

TEST_CASE("TI_TRANSPORT_020_RestartAfterStop", "[mcp][transport]") {
    auto config = makeEchoServerConfig();
    StdioTransport transport(config);

    // First start/stop
    transport.start();
    transport.stop();
    REQUIRE(transport.isRunning() == false);

    // Second start
    bool restarted = transport.start();
    REQUIRE(restarted == true);
    REQUIRE(transport.isRunning() == true);

    // Verify it works
    JsonRpcRequest request;
    request.id = 1;
    request.method = "test";
    auto response = transport.sendRequest(request, 5000);
    REQUIRE(response.isError() == false);

    transport.stop();
}
