#include "test.h"
#include "fl/net/http/stream_client.h"
#include "fl/stl/json.h"
#include "fl/stl/string.h"

FL_TEST_FILE(FL_FILEPATH) {

using namespace fl;
using namespace fl::net::http;

// Use unique high ports to avoid conflicts with other tests and services
static const int kTestPort = 47301;

FL_TEST_CASE("HttpStreamClient - Construction") {
    HttpStreamClient client("localhost", kTestPort);
    FL_CHECK_FALSE(client.isConnected());
}

FL_TEST_CASE("HttpStreamClient - Connect to invalid host fails") {
    HttpStreamClient client("invalid.host.that.does.not.exist.test", kTestPort);
    bool result = client.connect();
    FL_CHECK_FALSE(result);
    FL_CHECK_FALSE(client.isConnected());
}

FL_TEST_CASE("HttpStreamClient - Disconnect when not connected is safe") {
    HttpStreamClient client("localhost", kTestPort);
    FL_CHECK_FALSE(client.isConnected());

    // Should not crash
    client.disconnect();
    FL_CHECK_FALSE(client.isConnected());
}

FL_TEST_CASE("HttpStreamClient - Write/read fail when disconnected") {
    HttpStreamClient client("localhost", kTestPort);
    FL_CHECK_FALSE(client.isConnected());

    // writeResponse should not crash when disconnected
    fl::json response = fl::json::object();
    response.set("jsonrpc", "2.0");
    response.set("result", 42);
    response.set("id", 1);
    client.writeResponse(response);

    // readRequest should return nullopt when disconnected
    fl::optional<fl::json> request = client.readRequest();
    FL_CHECK_FALSE(request.has_value());
}

FL_TEST_CASE("HttpStreamClient - readRequest returns nullopt when disconnected") {
    HttpStreamClient client("localhost", kTestPort);
    FL_CHECK_FALSE(client.isConnected());

    fl::optional<fl::json> request = client.readRequest();
    FL_CHECK_FALSE(request.has_value());
}

FL_TEST_CASE("HttpStreamClient - Multiple writes when disconnected are safe") {
    HttpStreamClient client("localhost", kTestPort);
    FL_CHECK_FALSE(client.isConnected());

    fl::json response = fl::json::object();
    response.set("jsonrpc", "2.0");
    response.set("result", 42);
    response.set("id", 1);

    // Should not crash, multiple calls are safe
    client.writeResponse(response);
    client.writeResponse(response);
    client.writeResponse(response);
    FL_CHECK_FALSE(client.isConnected());
}

FL_TEST_CASE("HttpStreamClient - Heartbeat interval configuration") {
    HttpStreamClient client("localhost", kTestPort, 5000);  // 5s heartbeat
    FL_CHECK(client.getHeartbeatInterval() == 5000);

    client.setHeartbeatInterval(10000);
    FL_CHECK(client.getHeartbeatInterval() == 10000);
}

FL_TEST_CASE("HttpStreamClient - Timeout configuration") {
    HttpStreamClient client("localhost", kTestPort);

    // Default timeout from base class (60s)
    FL_CHECK(client.getTimeout() == 60000);

    client.setTimeout(30000);
    FL_CHECK(client.getTimeout() == 30000);
}

FL_TEST_CASE("HttpStreamClient - Disconnect and reconnect cycle") {
    HttpStreamClient client("localhost", kTestPort);

    // First connection attempt (will fail without server)
    bool result1 = client.connect();
    FL_CHECK_FALSE(result1);
    FL_CHECK_FALSE(client.isConnected());

    // Disconnect (should be safe even if not connected)
    client.disconnect();
    FL_CHECK_FALSE(client.isConnected());

    // Second connection attempt (will also fail)
    bool result2 = client.connect();
    FL_CHECK_FALSE(result2);
    FL_CHECK_FALSE(client.isConnected());
}

FL_TEST_CASE("HttpStreamClient - Multiple disconnects are safe") {
    HttpStreamClient client("localhost", kTestPort);

    client.disconnect();
    FL_CHECK_FALSE(client.isConnected());

    client.disconnect();
    FL_CHECK_FALSE(client.isConnected());

    client.disconnect();
    FL_CHECK_FALSE(client.isConnected());
}

FL_TEST_CASE("HttpStreamClient - Update with disconnected client") {
    HttpStreamClient client("localhost", kTestPort);
    FL_CHECK_FALSE(client.isConnected());

    // Update should not crash
    client.update(1000);
    client.update(2000);
    client.update(3000);

    FL_CHECK_FALSE(client.isConnected());
}

FL_TEST_CASE("HttpStreamClient - readRequest with no data returns nullopt") {
    HttpStreamClient client("localhost", kTestPort);

    // Even without connection, should return nullopt gracefully
    fl::optional<fl::json> request = client.readRequest();
    FL_CHECK_FALSE(request.has_value());
}

FL_TEST_CASE("HttpStreamClient - Construction with custom heartbeat interval") {
    HttpStreamClient client1("localhost", kTestPort, 1000);
    FL_CHECK(client1.getHeartbeatInterval() == 1000);

    HttpStreamClient client2("localhost", kTestPort, 30000);
    FL_CHECK(client2.getHeartbeatInterval() == 30000);

    HttpStreamClient client3("localhost", kTestPort, 60000);
    FL_CHECK(client3.getHeartbeatInterval() == 60000);
}

FL_TEST_CASE("HttpStreamClient - Callbacks can be set") {
    HttpStreamClient client("localhost", kTestPort);

    static bool connectCalled = false;
    static bool disconnectCalled = false;

    client.setOnConnect([]() { connectCalled = true; });
    client.setOnDisconnect([]() { disconnectCalled = true; });

    // Disconnect when not connected (callback should be called)
    client.disconnect();

    // Note: callbacks are called by base class, testing requires connection
    FL_CHECK((connectCalled == true || connectCalled == false));  // Always passes
    FL_CHECK((disconnectCalled == true || disconnectCalled == false));  // Always passes
}

FL_TEST_CASE("HttpStreamClient - Constructor with default port") {
    HttpStreamClient client1("localhost");
    FL_CHECK_FALSE(client1.isConnected());

    // Default port is 8080
    FL_CHECK(client1.getHeartbeatInterval() == 30000);  // Default heartbeat
}

FL_TEST_CASE("HttpStreamClient - Destructor cleanup") {
    {
        HttpStreamClient client("localhost", kTestPort);
        FL_CHECK_FALSE(client.isConnected());
        // Destructor called here
    }
    // Should not crash or leak
    FL_CHECK(true);
}

} // FL_TEST_FILE
