/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the 
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0 
********************************************************************************/

#if defined(_WIN32)

#include "gtest/gtest.h"

#include <support/app_control.h>
#include <interfaces/ipc.h>

#include "../../../sdv_services/uds_win_sockets/channel_mgnt.h"
#include "../../../sdv_services/uds_win_sockets/connection.h"

#include <windows.h>
#include <afunix.h>

#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstring>
#include <mutex>
#include <random>
#include <sstream>
#include <string>
#include <thread>

//  Helper namespace
namespace test_utils {

/**
 * @brief Ensure the directory for the given full path exists
 */
inline void EnsureParentDir(const std::string& fullPath)
{
    auto pos = fullPath.find_last_of("\\/");
    if (pos == std::string::npos)
        return;

    std::string dir = fullPath.substr(0, pos);
    CreateDirectoryA(dir.c_str(), nullptr);
}

/**
 * @brief Expand environment variables such as %LOCALAPPDATA%
 */
inline std::string Expand(const std::string& in)
{
    if (in.find('%') == std::string::npos)
        return in;

    char buf[4096] = {};
    DWORD n = ExpandEnvironmentStringsA(in.c_str(), buf, sizeof(buf));
    return (n > 0 && n < sizeof(buf)) ? std::string(buf) : in;
}

/**
 * @brief Build a short Win32 path for UDS sockets
 */
inline std::string MakeShortUdsPath(const char* name)
{
    std::string base = R"(%LOCALAPPDATA%\sdv\)";
    base = Expand(base);
    EnsureParentDir(base);
    return base + name;
}

/**
 * @brief Generate a random hex suffix for unique paths
 */
inline std::string RandomHex()
{
    std::mt19937_64 rng{ std::random_device{}() };
    std::uniform_int_distribution<uint64_t> dist;
    std::ostringstream oss;
    oss << std::hex << dist(rng);
    return oss.str();
}

/**
 * @brief Unique absolute path (keeps tests independent)
 */
inline std::string UniqueUds(const char* prefix)
{
    return MakeShortUdsPath((std::string(prefix) + "_" + RandomHex() + ".sock").c_str());
}

/**
 * @brief Wait until a server connection worker has reached "armed" state
 */
inline void SpinUntilServerArmed(sdv::ipc::IConnect* server, uint32_t maxWaitMs = 300)
{
    using namespace std::chrono;
    auto deadline = steady_clock::now() + milliseconds(maxWaitMs);

    while (server->GetConnectState() == sdv::ipc::EConnectState::uninitialized &&
           steady_clock::now() < deadline)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
}

/**
 * @brief Small sleep helper for teardown time
 */
inline void SleepTiny(uint32_t ms = 20)
{
    std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}

} // namespace test_utils

//  Unified test receiver (state + data)
class CTestReceiver :
    public sdv::IInterfaceAccess,
    public sdv::ipc::IConnectEventCallback,
    public sdv::ipc::IDataReceiveCallback
{
public:
    BEGIN_SDV_INTERFACE_MAP()
        SDV_INTERFACE_ENTRY(sdv::ipc::IDataReceiveCallback)
        SDV_INTERFACE_ENTRY(sdv::ipc::IConnectEventCallback)
    END_SDV_INTERFACE_MAP()

    void SetConnectState(sdv::ipc::EConnectState s) override
    {
        {
            std::lock_guard<std::mutex> lk(m_mtx);
            m_state = s;
        }
        m_cv.notify_all();
    }

    void ReceiveData(sdv::sequence<sdv::pointer<uint8_t>>& seq) override
    {
        {
            std::lock_guard<std::mutex> lk(m_mtx);
            m_data = seq;
            m_hasData = true;
        }
        m_cv.notify_all();
    }

    bool WaitForState(sdv::ipc::EConnectState expected, uint32_t ms = 2000)
    {
        std::unique_lock<std::mutex> lk(m_mtx);
        return m_cv.wait_for(lk, std::chrono::milliseconds(ms),
                             [&]{ return m_state == expected; });
    }

    bool WaitForData(uint32_t ms = 2000)
    {
        std::unique_lock<std::mutex> lk(m_mtx);
        return m_cv.wait_for(lk, std::chrono::milliseconds(ms),
                             [&]{ return m_hasData; });
    }

    sdv::sequence<sdv::pointer<uint8_t>> Data()
    {
        std::lock_guard<std::mutex> lk(m_mtx);
        return m_data;
    }

private:
    mutable std::mutex m_mtx;
    std::condition_variable m_cv;

    sdv::ipc::EConnectState m_state{ sdv::ipc::EConnectState::uninitialized };
    sdv::sequence<sdv::pointer<uint8_t>> m_data;
    bool m_hasData{false};
};


//  Helper for creating server + client on a given UDS connect-string
struct ServerClient
{
    sdv::TObjectPtr serverObj;
    sdv::ipc::IConnect* server = nullptr;
    sdv::TObjectPtr clientObj;
    sdv::ipc::IConnect* client = nullptr;
};

static ServerClient CreatePair(
    CSocketsChannelMgnt& mgr,
    const sdv::ipc::SChannelEndpoint& ep)
{
    ServerClient out;

    // Server: use the connection returned by CreateEndpoint().
    // Do not create another server with Access(... role=server),
    // otherwise we create a duplicate listener on the same UDS path.
    out.serverObj = ep.pConnection;
    out.server = out.serverObj
        ? out.serverObj.GetInterface<sdv::ipc::IConnect>()
        : nullptr;

    // Client: connect through the endpoint connect string.
    out.clientObj = mgr.Access(ep.ssConnectString);
    out.client = out.clientObj
        ? out.clientObj.GetInterface<sdv::ipc::IConnect>()
        : nullptr;

    return out;
}

//  TESTS START HERE
using namespace test_utils;

// Instantiate manager
TEST(WindowsAFUnixIPC, Instantiate)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(""));

    CSocketsChannelMgnt mgr;
    EXPECT_NO_THROW(mgr.Initialize(sdv::SObjectInfo()));
    EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized);
    EXPECT_NO_THROW(mgr.Shutdown());
    EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::destruction_pending);

    app.Shutdown();
}

// Basic connect/disconnect (server + client)
TEST(WindowsAFUnixIPC, BasicConnectDisconnect)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(""));
    app.SetRunningMode();

    CSocketsChannelMgnt mgr;
    mgr.Initialize(sdv::SObjectInfo());
    mgr.SetOperationMode(sdv::EOperationMode::running);

    const std::string path = UniqueUds("basic");
    const std::string cs = std::string("proto=uds;path=") + path + ";";

    // Create endpoint
    auto ep = mgr.CreateEndpoint(cs);
    ASSERT_FALSE(ep.ssConnectString.empty());

    // Create server + client objects
    auto pair = CreatePair(mgr, ep);
    ASSERT_NE(pair.server, nullptr);
    ASSERT_NE(pair.client, nullptr);

    CTestReceiver sr, cr;

    // Server side connect
    pair.server->AsyncConnect(&sr);
    SpinUntilServerArmed(pair.server);

    // Client side connect
    pair.client->AsyncConnect(&cr);

    EXPECT_TRUE(pair.server->WaitForConnection(5000));
    EXPECT_TRUE(pair.client->WaitForConnection(5000));

    pair.client->Disconnect();
    pair.server->Disconnect();

    mgr.Shutdown();
    app.Shutdown();
}

// Simple data path: client -> server sends "hello"
TEST(WindowsAFUnixIPC, DataPath_SimpleHello)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(""));
    app.SetRunningMode();

    CSocketsChannelMgnt mgr;
    mgr.Initialize(sdv::SObjectInfo());
    mgr.SetOperationMode(sdv::EOperationMode::running);

    const std::string cs = std::string("proto=uds;path=") + UniqueUds("hello") + ";";
    auto ep = mgr.CreateEndpoint(cs);
    auto pair = CreatePair(mgr, ep);

    ASSERT_NE(pair.server, nullptr);
    ASSERT_NE(pair.client, nullptr);

    CTestReceiver sr, cr;

    pair.server->AsyncConnect(&sr);
    SpinUntilServerArmed(pair.server);

    pair.client->AsyncConnect(&cr);

    ASSERT_TRUE(pair.server->WaitForConnection(5000));
    ASSERT_TRUE(pair.client->WaitForConnection(5000));

    // Build hello payload
    sdv::pointer<uint8_t> p;
    p.resize(5);
    std::memcpy(p.get(), "hello", 5);

    sdv::sequence<sdv::pointer<uint8_t>> seq;
    seq.push_back(p);

    auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
    ASSERT_NE(sender, nullptr);

    EXPECT_TRUE(sender->SendData(seq));
    EXPECT_TRUE(sr.WaitForData(3000));

    auto recv = sr.Data();
    ASSERT_EQ(recv.size(), 1u);
    ASSERT_EQ(recv[0].size(), 5u);
    EXPECT_EQ(std::memcmp(recv[0].get(), "hello", 5), 0);

    pair.client->Disconnect();
    pair.server->Disconnect();

    mgr.Shutdown();
    app.Shutdown();
}

// Server disconnect propagates to client
TEST(WindowsAFUnixIPC, ServerDisconnectPropagates)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(""));
    app.SetRunningMode();

    CSocketsChannelMgnt mgr;
    mgr.Initialize(sdv::SObjectInfo());
    mgr.SetOperationMode(sdv::EOperationMode::running);

    const std::string cs = std::string("proto=uds;path=") + UniqueUds("disc") + ";";
    auto ep = mgr.CreateEndpoint(cs);

    auto pair = CreatePair(mgr, ep);
    ASSERT_NE(pair.server, nullptr);
    ASSERT_NE(pair.client, nullptr);

    CTestReceiver sr, cr;

    pair.server->AsyncConnect(&sr);
    SpinUntilServerArmed(pair.server);

    pair.client->AsyncConnect(&cr);

    ASSERT_TRUE(pair.server->WaitForConnection(5000));
    ASSERT_TRUE(pair.client->WaitForConnection(5000));

    pair.server->Disconnect();

    EXPECT_TRUE(cr.WaitForState(sdv::ipc::EConnectState::disconnected, 3000));

    pair.client->Disconnect();

    mgr.Shutdown();
    app.Shutdown();
}

// DataPath – multi-chunk payload
TEST(WindowsAFUnixIPC, DataPath_MultiChunk)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(""));
    app.SetRunningMode();

    CSocketsChannelMgnt mgr;
    mgr.Initialize(sdv::SObjectInfo());
    mgr.SetOperationMode(sdv::EOperationMode::running);

    const std::string cs = std::string("proto=uds;path=") + UniqueUds("mc") + ";";

    auto ep = mgr.CreateEndpoint(cs);
    auto pair = CreatePair(mgr, ep);
    ASSERT_NE(pair.server, nullptr);
    ASSERT_NE(pair.client, nullptr);

    CTestReceiver sr, cr;

    pair.server->AsyncConnect(&sr);
    SpinUntilServerArmed(pair.server);

    pair.client->AsyncConnect(&cr);

    ASSERT_TRUE(pair.server->WaitForConnection(5000));
    ASSERT_TRUE(pair.client->WaitForConnection(5000));

    // Build multichunk
    sdv::pointer<uint8_t> p1, p2;
    p1.resize(3);  std::memcpy(p1.get(), "sdv", 3);
    p2.resize(9);  std::memcpy(p2.get(), "framework", 9);

    sdv::sequence<sdv::pointer<uint8_t>> seq;
    seq.push_back(p1);
    seq.push_back(p2);

    auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
    ASSERT_NE(sender, nullptr);
    EXPECT_TRUE(sender->SendData(seq));

    ASSERT_TRUE(sr.WaitForData(3000));
    auto recv = sr.Data();

    ASSERT_EQ(recv.size(), 2u);
    EXPECT_EQ(std::memcmp(recv[0].get(), "sdv", 3), 0);
    EXPECT_EQ(std::memcmp(recv[1].get(), "framework", 9), 0);

    pair.client->Disconnect();
    pair.server->Disconnect();

    mgr.Shutdown();
    app.Shutdown();
}

// Large payload fragmentation + reassembly
TEST(WindowsAFUnixIPC, DataPath_LargePayloadFragmentation)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(""));
    app.SetRunningMode();

    CSocketsChannelMgnt mgr;
    mgr.Initialize(sdv::SObjectInfo());
    mgr.SetOperationMode(sdv::EOperationMode::running);

    const std::string cs = std::string("proto=uds;path=") + UniqueUds("big") + ";";

    auto ep = mgr.CreateEndpoint(cs);
    auto pair = CreatePair(mgr, ep);

    ASSERT_NE(pair.server, nullptr);
    ASSERT_NE(pair.client, nullptr);

    CTestReceiver sr, cr;

    pair.server->AsyncConnect(&sr);
    SpinUntilServerArmed(pair.server);

    pair.client->AsyncConnect(&cr);

    ASSERT_TRUE(pair.server->WaitForConnection(5000));
    ASSERT_TRUE(pair.client->WaitForConnection(5000));

    const size_t N = 256 * 1024;
    sdv::pointer<uint8_t> payload;
    payload.resize(N);
    for (size_t i = 0; i < N; ++i)
        payload.get()[i] = uint8_t(i & 0xFF);

    sdv::sequence<sdv::pointer<uint8_t>> seq;
    seq.push_back(payload);

    auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
    ASSERT_NE(sender, nullptr);

    EXPECT_TRUE(sender->SendData(seq));
    ASSERT_TRUE(sr.WaitForData(5000));

    auto recv = sr.Data();
    ASSERT_EQ(recv.size(), 1u);
    ASSERT_EQ(recv[0].size(), N);
    EXPECT_EQ(std::memcmp(recv[0].get(), payload.get(), N), 0);

    pair.client->Disconnect();
    pair.server->Disconnect();

    mgr.Shutdown();
    app.Shutdown();
}

// Zero-length chunks preserved
TEST(WindowsAFUnixIPC, DataPath_ZeroLengthChunks)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(""));
    app.SetRunningMode();

    CSocketsChannelMgnt mgr;
    mgr.Initialize(sdv::SObjectInfo());
    mgr.SetOperationMode(sdv::EOperationMode::running);

    const std::string cs = std::string("proto=uds;path=") + UniqueUds("zlen") + ";";
    auto ep = mgr.CreateEndpoint(cs);
    auto pair = CreatePair(mgr, ep);

    ASSERT_NE(pair.server, nullptr);
    ASSERT_NE(pair.client, nullptr);

    CTestReceiver sr, cr;

    pair.server->AsyncConnect(&sr);
    SpinUntilServerArmed(pair.server);

    pair.client->AsyncConnect(&cr);

    ASSERT_TRUE(pair.server->WaitForConnection(5000));
    ASSERT_TRUE(pair.client->WaitForConnection(5000));

    sdv::pointer<uint8_t> p0a, p5, p0b, p8;
    p0a.resize(0);
    p5.resize(5); std::memcpy(p5.get(), "world",5);
    p0b.resize(0);
    p8.resize(8); std::memcpy(p8.get(), "fragment",8);

    sdv::sequence<sdv::pointer<uint8_t>> seq;
    seq.push_back(p0a);
    seq.push_back(p5);
    seq.push_back(p0b);
    seq.push_back(p8);

    auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
    ASSERT_NE(sender, nullptr);
    EXPECT_TRUE(sender->SendData(seq));

    ASSERT_TRUE(sr.WaitForData(3000));
    auto recv = sr.Data();

    ASSERT_EQ(recv.size(), 4u);
    EXPECT_EQ(recv[0].size(), 0u);
    EXPECT_EQ(recv[1].size(), 5u);
    EXPECT_EQ(recv[2].size(), 0u);
    EXPECT_EQ(recv[3].size(), 8u);

    pair.client->Disconnect();
    pair.server->Disconnect();

    mgr.Shutdown();
    app.Shutdown();
}

// Operation mode transitions
TEST(WindowsAFUnixIPC, OperationModeTransitions)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(""));

    CSocketsChannelMgnt mgr;
    mgr.Initialize(sdv::SObjectInfo());

    EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized);
    mgr.SetOperationMode(sdv::EOperationMode::configuring);
    EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::configuring);
    mgr.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);

    mgr.Shutdown();
    EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::destruction_pending);

    app.Shutdown();
}

// Reconnect using the same UDS path (two consecutive sessions)
TEST(WindowsAFUnixIPC, ReconnectAfterDisconnect_SamePath)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(""));
    app.SetRunningMode();

    CSocketsChannelMgnt mgr;
    mgr.Initialize(sdv::SObjectInfo());
    mgr.SetOperationMode(sdv::EOperationMode::running);

    const std::string udsPath = MakeShortUdsPath(("vapi_win_reconn_" + RandomHex() + ".sock").c_str());

    const std::string cs = std::string("proto=uds;path=") + udsPath + ";";

    // ----- Session 1 -----
    {
        auto ep = mgr.CreateEndpoint(cs);
        auto pair = CreatePair(mgr, ep);
        ASSERT_NE(pair.server, nullptr);
        ASSERT_NE(pair.client, nullptr);

        CTestReceiver sr, cr;
        pair.server->AsyncConnect(&sr);
        SpinUntilServerArmed(pair.server);

        std::atomic<int> clientRes{0};

        sdv::core::secure_thread ct([&]{
            if (!pair.clientObj) { clientRes = 1; return; }
            auto* c = pair.client;
            if (!c) { clientRes = 2; return; }
            c->AsyncConnect(&cr);
            if (!c->WaitForConnection(5000)) { clientRes = 3; return; }
            c->Disconnect();
            clientRes = 0;
        });

        EXPECT_TRUE(pair.server->WaitForConnection(5000));
        ct.join();
        EXPECT_EQ(clientRes.load(), 0);

        pair.server->Disconnect();
    }

    SleepTiny(20);

    // ----- Session 2 -----
    {
        auto ep = mgr.CreateEndpoint(cs);
        auto pair = CreatePair(mgr, ep);
        ASSERT_NE(pair.server, nullptr);
        ASSERT_NE(pair.client, nullptr);

        CTestReceiver sr, cr;
        pair.server->AsyncConnect(&sr);
        SpinUntilServerArmed(pair.server);

        std::atomic<int> clientRes{0};

        sdv::core::secure_thread ct([&]{
            if (!pair.clientObj) { clientRes = 1; return; }
            auto* c = pair.client;
            if (!c) { clientRes = 2; return; }
            c->AsyncConnect(&cr);
            if (!c->WaitForConnection(5000)) { clientRes = 3; return; }
            c->Disconnect();
            clientRes = 0;
        });

        EXPECT_TRUE(pair.server->WaitForConnection(5000));
        ct.join();
        EXPECT_EQ(clientRes.load(), 0);

        pair.server->Disconnect();
    }

    mgr.Shutdown();
    app.Shutdown();
}

// WaitForConnection(INFINITE) with a delayed client
TEST(WindowsAFUnixIPC, WaitForConnection_InfiniteWait_SlowClient)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(""));
    app.SetRunningMode();

    CSocketsChannelMgnt mgr;
    mgr.Initialize(sdv::SObjectInfo());
    mgr.SetOperationMode(sdv::EOperationMode::running);

    const std::string cs = std::string("proto=uds;path=") + MakeShortUdsPath(("vapi_win_slow_" + RandomHex() + ".sock").c_str()) + ";";
    auto ep = mgr.CreateEndpoint(cs);

    sdv::TObjectPtr sObj = mgr.Access(ep.ssConnectString);
    auto* server = sObj.GetInterface<sdv::ipc::IConnect>();
    ASSERT_NE(server, nullptr);

    CTestReceiver sr;
    server->AsyncConnect(&sr);
    SpinUntilServerArmed(server);

    sdv::core::secure_thread delayedClient([&]{
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        sdv::TObjectPtr cObj = mgr.Access(ep.ssConnectString);
        auto* client = cObj.GetInterface<sdv::ipc::IConnect>();
        CTestReceiver cr;
        client->AsyncConnect(&cr);
        client->WaitForConnection(5000);
        client->Disconnect();
    });

    EXPECT_TRUE(server->WaitForConnection(6000));
    delayedClient.join();
    server->Disconnect();

    mgr.Shutdown();
    app.Shutdown();
}

// WaitForConnection(0) before and after establishing the connection
TEST(WindowsAFUnixIPC, WaitForConnection_ZeroTimeout_BeforeAndAfter)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(""));
    app.SetRunningMode();

    CSocketsChannelMgnt mgr;
    mgr.Initialize(sdv::SObjectInfo());
    mgr.SetOperationMode(sdv::EOperationMode::running);

    const std::string cs =
        std::string("proto=uds;path=") +
        MakeShortUdsPath(("vapi_win_zero_" + RandomHex() + ".sock").c_str()) +
        ";";

    auto ep = mgr.CreateEndpoint(cs);
    sdv::TObjectPtr sObj = mgr.Access(ep.ssConnectString);
    auto* server = sObj.GetInterface<sdv::ipc::IConnect>();

    ASSERT_NE(server, nullptr);

    CTestReceiver sr;
    server->AsyncConnect(&sr);
    SpinUntilServerArmed(server);

    EXPECT_FALSE(server->WaitForConnection(0));

    sdv::TObjectPtr cObj = mgr.Access(ep.ssConnectString);
    auto* client = cObj.GetInterface<sdv::ipc::IConnect>();

    CTestReceiver cr;
    client->AsyncConnect(&cr);

    EXPECT_TRUE(server->WaitForConnection(5000));
    EXPECT_TRUE(client->WaitForConnection(5000));

    client->Disconnect();
    server->Disconnect();

    mgr.Shutdown();
    app.Shutdown();
}

// Client timeout when NO server exists on that path
TEST(WindowsAFUnixIPC, ClientTimeout_NoServer)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(R"toml([LogHandler]
ViewFilter = "Fatal")toml"));
    app.SetRunningMode();

    CSocketsChannelMgnt mgr;
    mgr.Initialize(sdv::SObjectInfo());
    mgr.SetOperationMode(sdv::EOperationMode::running);

    const std::string raw = MakeShortUdsPath(("vapi_win_nosrv_" + RandomHex() + ".sock").c_str());
    const std::string cs = std::string("proto=uds;path=") + raw + ";";

    sdv::TObjectPtr cObj = mgr.Access(cs);

    if (!cObj)
    {
        SUCCEED() << "Access(proto=uds;path=..., no-server) may return nullptr immediately on Windows.";
        mgr.Shutdown();
        app.Shutdown();
        return;
    }

    auto* client = cObj.GetInterface<sdv::ipc::IConnect>();
    ASSERT_NE(client, nullptr);

    CTestReceiver cr;
    client->AsyncConnect(&cr);

    EXPECT_FALSE(client->WaitForConnection(1500));
    std::this_thread::sleep_for(std::chrono::milliseconds(800));

    const auto finalState = client->GetConnectState();
    EXPECT_TRUE(finalState == sdv::ipc::EConnectState::connection_error ||
                finalState == sdv::ipc::EConnectState::disconnected ||
                finalState == sdv::ipc::EConnectState::initializing)
        << "Expected timeout-related terminal or pending state without server.";

    client->Disconnect();
    mgr.Shutdown();
    app.Shutdown();
}

// Server closes the connection during a large transfer → client sees disconnected
TEST(WindowsAFUnixIPC, PeerCloseMidTransfer_ClientDetectsDisconnect)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(""));
    app.SetRunningMode();

    CSocketsChannelMgnt mgr;
    mgr.Initialize(sdv::SObjectInfo());
    mgr.SetOperationMode(sdv::EOperationMode::running);

    const std::string cs =
        std::string("proto=uds;path=") +
        MakeShortUdsPath(("vapi_win_midclose_" + RandomHex() + ".sock").c_str()) +
        ";";

    auto ep = mgr.CreateEndpoint(cs);
    auto pair = CreatePair(mgr, ep);
    ASSERT_NE(pair.server, nullptr);
    ASSERT_NE(pair.client, nullptr);

    CTestReceiver sr, cr;

    pair.server->AsyncConnect(&sr);
    SpinUntilServerArmed(pair.server);
    pair.client->AsyncConnect(&cr);

    ASSERT_TRUE(pair.server->WaitForConnection(5000));
    ASSERT_TRUE(pair.client->WaitForConnection(5000));

    sdv::pointer<uint8_t> buf;
    buf.resize(512 * 1024);
    std::memset(buf.get(), 0xAA, buf.size());

    sdv::sequence<sdv::pointer<uint8_t>> seq;
    seq.push_back(buf);

    auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
    ASSERT_NE(sender, nullptr);

    std::atomic<bool> sendOk{true};
    sdv::core::secure_thread t([&]{
        sendOk.store(sender->SendData(seq));
    });

    std::this_thread::sleep_for(std::chrono::milliseconds(50));
    pair.server->Disconnect();

    t.join();

    EXPECT_TRUE(cr.WaitForState(sdv::ipc::EConnectState::disconnected, 3000));

    pair.client->Disconnect();
    mgr.Shutdown();
    app.Shutdown();
}

// Client cancels connect attempt when no server exists → must clean up promptly
TEST(WindowsAFUnixIPC, ClientCancelConnect_NoServer_Cleanup)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(R"toml([LogHandler]
ViewFilter = "Fatal")toml"));
    app.SetRunningMode();

    CSocketsChannelMgnt mgr;
    mgr.Initialize(sdv::SObjectInfo());
    mgr.SetOperationMode(sdv::EOperationMode::running);

    const std::string raw = MakeShortUdsPath(("vapi_win_cancel_" + RandomHex() + ".sock").c_str());
    const std::string cs = std::string("proto=uds;path=") + raw + ";";
    sdv::TObjectPtr cObj = mgr.Access(cs);

    if (!cObj)
    {
        SUCCEED() << "Immediate nullptr for no-server case is valid Windows behavior.";
        mgr.Shutdown();
        app.Shutdown();
        return;
    }

    auto* client = cObj.GetInterface<sdv::ipc::IConnect>();
    ASSERT_NE(client, nullptr);

    CTestReceiver cr;
    client->AsyncConnect(&cr);

    std::this_thread::sleep_for(std::chrono::milliseconds(150));
    client->Disconnect();

    EXPECT_EQ(client->GetConnectState(), sdv::ipc::EConnectState::disconnected);

    mgr.Shutdown();
    app.Shutdown();
}

// Server starts and immediately disconnects (no client)
TEST(WindowsAFUnixIPC, ServerStartThenImmediateDisconnect_NoClient)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(R"toml([LogHandler]
ViewFilter = "Fatal")toml"));
    app.SetRunningMode();

    CSocketsChannelMgnt mgr;
    mgr.Initialize(sdv::SObjectInfo());
    mgr.SetOperationMode(sdv::EOperationMode::running);

    const std::string cs =
        std::string("proto=uds;path=") +
        MakeShortUdsPath(("vapi_win_srvonly_" + RandomHex() + ".sock").c_str()) +
        ";";

    auto ep = mgr.CreateEndpoint(cs);
    sdv::TObjectPtr sObj = mgr.Access(ep.ssConnectString);
    auto* server = sObj.GetInterface<sdv::ipc::IConnect>();
    ASSERT_NE(server, nullptr);

    CTestReceiver sr;
    server->AsyncConnect(&sr);
    SpinUntilServerArmed(server);

    std::this_thread::sleep_for(std::chrono::milliseconds(50));

    server->Disconnect();
    EXPECT_EQ(server->GetConnectState(), sdv::ipc::EConnectState::disconnected);

    mgr.Shutdown();
    app.Shutdown();
}

// UnregisterStateEventCallback: ensure single-listener semantics
TEST(WindowsAFUnixIPC, UnregisterStateEventCallback_SingleListenerSemantics)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(""));
    app.SetRunningMode();

    CSocketsChannelMgnt mgr;
    mgr.Initialize(sdv::SObjectInfo());
    mgr.SetOperationMode(sdv::EOperationMode::running);

    const std::string cs =
        std::string("proto=uds;path=") +
        MakeShortUdsPath(("vapi_win_cb_" + RandomHex() + ".sock").c_str()) + ";";

    auto ep = mgr.CreateEndpoint(cs);

    sdv::TObjectPtr sObj = mgr.Access(ep.ssConnectString);
    auto* server = sObj.GetInterface<sdv::ipc::IConnect>();
    ASSERT_NE(server, nullptr);

    sdv::TObjectPtr cObj = mgr.Access(ep.ssConnectString);
    auto* client = cObj.GetInterface<sdv::ipc::IConnect>();
    ASSERT_NE(client, nullptr);

    CTestReceiver regListener;
    const uint64_t cookie = server->RegisterStateEventCallback(&regListener);
    ASSERT_NE(cookie, 0u);

    CTestReceiver mainRecv;

    server->AsyncConnect(&mainRecv);
    client->AsyncConnect(&mainRecv);
    SpinUntilServerArmed(server);

    EXPECT_TRUE(server->WaitForConnection(5000));
    EXPECT_TRUE(client->WaitForConnection(5000));

    EXPECT_TRUE(mainRecv.WaitForState(sdv::ipc::EConnectState::connected, 1000));
    EXPECT_FALSE(regListener.WaitForState(sdv::ipc::EConnectState::connected, 300)) << "The registry listener should NOT receive events while main receiver is active.";

    server->UnregisterStateEventCallback(cookie);

    client->Disconnect();
    EXPECT_TRUE(mainRecv.WaitForState(sdv::ipc::EConnectState::disconnected, 1500));

    EXPECT_FALSE(regListener.WaitForState(sdv::ipc::EConnectState::disconnected, 300));

    server->Disconnect();
    mgr.Shutdown();
    app.Shutdown();
}

// Reconnect race regression: second AsyncConnect during initializing must fail fast,
// not block by joining an in-progress connect worker.
TEST(WindowsAFUnixIPC, AsyncConnect_DoubleCallWhileInitializing_NoDeadlock)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(""));
    app.SetRunningMode();

    CSocketsChannelMgnt mgr;
    mgr.Initialize(sdv::SObjectInfo());
    mgr.SetOperationMode(sdv::EOperationMode::running);

    const std::string cs =
        std::string("proto=uds;path=") +
        MakeShortUdsPath(("vapi_win_race_" + RandomHex() + ".sock").c_str()) + ";";

    auto ep = mgr.CreateEndpoint(cs);
    ASSERT_FALSE(ep.ssConnectString.empty());

    auto pair = CreatePair(mgr, ep);
    ASSERT_NE(pair.server, nullptr);
    ASSERT_NE(pair.client, nullptr);

    CTestReceiver sr, cr;

    ASSERT_TRUE(pair.server->AsyncConnect(&sr));

    auto t0 = std::chrono::steady_clock::now();
    bool secondAsync = pair.server->AsyncConnect(&sr);
    auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(
        std::chrono::steady_clock::now() - t0).count();

    EXPECT_FALSE(secondAsync);
    EXPECT_LT(elapsedMs, 200);

    ASSERT_TRUE(pair.client->AsyncConnect(&cr));
    EXPECT_TRUE(pair.server->WaitForConnection(5000));
    EXPECT_TRUE(pair.client->WaitForConnection(5000));

    // Once connected, repeated AsyncConnect should be a no-op success.
    EXPECT_TRUE(pair.server->AsyncConnect(&sr));

    pair.client->Disconnect();
    pair.server->Disconnect();

    mgr.Shutdown();
    app.Shutdown();
}

// CreateEndpoint with very long path → must be normalized to basename
TEST(WindowsAFUnixIPC, CreateEndpoint_LongInputPath_Normalized)
{
    sdv::app::CAppControl app;
    ASSERT_TRUE(app.Startup(""));
    app.SetRunningMode();

    CSocketsChannelMgnt mgr;
    mgr.Initialize(sdv::SObjectInfo());
    mgr.SetOperationMode(sdv::EOperationMode::running);

    std::string longName(160, 'A');

    const std::string rawPath = "C:\\Users\\" + longName + "\\AppData\\Local\\sdv\\vapi_win_long_" + RandomHex() + ".sock";
    const std::string cs = std::string("proto=uds;path=") + rawPath + ";";

    auto ep = mgr.CreateEndpoint(cs);

    ASSERT_FALSE(ep.ssConnectString.empty());

    const std::string publishedCS = ep.ssConnectString;
    EXPECT_NE(publishedCS.find("path=vapi_win_long_"), std::string::npos);

    sdv::TObjectPtr sObj = mgr.Access(publishedCS);
    auto* server = sObj.GetInterface<sdv::ipc::IConnect>();
    ASSERT_NE(server, nullptr);

    CTestReceiver sr;
    server->AsyncConnect(&sr);
    SpinUntilServerArmed(server);

    sdv::TObjectPtr cObj = mgr.Access(publishedCS);
    auto* client = cObj.GetInterface<sdv::ipc::IConnect>();
    ASSERT_NE(client, nullptr);

    CTestReceiver cr;
    client->AsyncConnect(&cr);

    EXPECT_TRUE(server->WaitForConnection(5000));
    EXPECT_TRUE(client->WaitForConnection(5000));

    client->Disconnect();
    server->Disconnect();

    mgr.Shutdown();
    app.Shutdown();
}

#endif // _WIN32