#include "lt.h"

#include <atomic>
#include <chrono>
#include <condition_variable>
#include <filesystem>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
#include <utility>
#include <vector>

#ifdef _WIN32
#include <windows.h>
#else
#include <arpa/inet.h>
#include <cstring>
#include <fcntl.h>
#include <netdb.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <unistd.h>
#define SOCKET int
#endif

namespace lt {

//---------------------------------------------------------------------------
// JSON helpers
//---------------------------------------------------------------------------

// Load boolean value from JSON with type safety
bool Load(const json& j, const std::string& key, bool& value)
{
    if (!j.contains(key) || !j.at(key).is_boolean()) {
        value = false;
        return false;
    }
    j.at(key).get_to(value);
    return true;
}

// Load string value from JSON with type safety
bool Load(const json& j, const std::string& key, std::string& value)
{
    if (!j.contains(key) || !j.at(key).is_string()) {
        value = "";
        return false;
    }
    j.at(key).get_to(value);
    return true;
}

// Load number value from JSON with type safety
template <typename T>
bool Load(const json& j, const std::string& key, T& value)
{
    if (!j.contains(key) || !j.at(key).is_number()) {
        value = 0;
        return false;
    }
    j.at(key).get_to(value);
    return true;
}

// Load base64 string value from JSON and decode
bool Load(const json& j, const std::string& key, std::vector<char>& value)
{
    if (!j.contains(key) || !j.at(key).is_string()) {
        value.clear();
        return false;
    }

    // Initialize decoding table
    std::vector<int> decodingTable(256, -1);
    const char* base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    for (int i = 0; i < 64; i++) {
        decodingTable[static_cast<unsigned char>(base64Chars[i])] = i;
    }

    // Decode base64 string
    int val = 0, valb = -8;
    const std::string& encodedData = j.at(key).get<std::string>();
    value.reserve(encodedData.length() * 3 / 4); // Approximate size

    for (char c : encodedData) {
        if (decodingTable[static_cast<unsigned char>(c)] == -1) {
            break;
        }
        val = (val << 6) + decodingTable[static_cast<unsigned char>(c)];
        valb += 6;
        if (valb >= 0) {
            value.push_back(static_cast<char>((val >> valb) & 0xFF));
            valb -= 8;
        }
    }
    return true;
}

// Load JSON value from JSON
bool Load(const json& j, const std::string& key, json& value)
{
    if (!j.contains(key)) {
        return false;
    }
    j.at(key).get_to(value);
    return true;
}

#ifdef _WIN32
static std::string getLastErrorString()
{
    LPVOID lpMsgBuf = nullptr;
    const DWORD bufLen = FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        nullptr,
        GetLastError(),
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        reinterpret_cast<LPTSTR>(&lpMsgBuf),
        0,
        nullptr);

    if (!lpMsgBuf) {
        return "Unknown error";
    }

    const LPCSTR lpMsgStr = static_cast<LPCSTR>(lpMsgBuf);
    std::string result(lpMsgStr, lpMsgStr + bufLen);

    // Cleanup allocated message buffer
    LocalFree(lpMsgBuf);

    // Trim trailing newlines and carriage returns
    while (!result.empty() && (result.back() == '\n' || result.back() == '\r')) {
        result.pop_back();
    }

    return result;
}
#endif


//---------------------------------------------------------------------------
// SharedBuffer
//---------------------------------------------------------------------------

class BufferObject {
public:
    BufferObject(const std::string& name, int size, error& err);
    ~BufferObject();

    // No copy allowed
    BufferObject(const BufferObject&) = delete;
    BufferObject& operator=(const BufferObject&) = delete;

    char* getData() const { return m_data; }
    int getSize() const { return m_size; }

    void incrementRef() { ++m_refCount; }
    int decrementRef() { return --m_refCount; }
    int getRefCount() const { return m_refCount; }

private:
    char* m_data;
    int m_size;
    int m_refCount;
#ifdef _WIN32
    HANDLE m_handle;
#else
    int m_fd;
#endif
};

#ifdef _WIN32

BufferObject::BufferObject(const std::string& name, int size, error& err)
    : m_data(nullptr)
    , m_size(0)
    , m_refCount(0)
    , m_handle(nullptr)
{
    err.clear();

    // Convert name to wide string for Windows API
    std::wstring wideName(name.begin(), name.end());

    // Open existing shared memory
    m_handle = OpenFileMappingW(
        FILE_MAP_READ,
        FALSE,
        wideName.c_str());

    if (m_handle == nullptr) {
        err = "Could not open shared memory: " + getLastErrorString();
        return;
    }

    // Map view of shared memory
    m_data = static_cast<char*>(MapViewOfFile(
        m_handle,
        FILE_MAP_READ,
        0,
        0,
        static_cast<SIZE_T>(size)));

    if (m_data == nullptr) {
        CloseHandle(m_handle);
        m_handle = nullptr;
        err = "Could not map shared memory: " + getLastErrorString();
        return;
    }

    m_size = size;
}

BufferObject::~BufferObject()
{
    if (m_data != nullptr) {
        UnmapViewOfFile(m_data);
        m_data = nullptr;
    }

    if (m_handle != nullptr) {
        CloseHandle(m_handle);
        m_handle = nullptr;
    }
}

#else // defined(_WIN32)

BufferObject::BufferObject(const std::string& name, int size, error& err)
    : m_data(nullptr)
    , m_size(0)
    , m_refCount(0)
    , m_fd(-1)
{
    err.clear();

    const int perm = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
    const std::string fullPath = "/dev/shm/" + name;

    // Open shared memory file
    m_fd = open(fullPath.c_str(), O_RDWR, perm);

    if (m_fd < 0) {
        err = "Could not open shared memory file: " + std::string(strerror(errno));
        return;
    }

    // Set file size
    if (ftruncate(m_fd, size) < 0) {
        close(m_fd);
        m_fd = -1;
        err = "Could not set shared memory size: " + std::string(strerror(errno));
        return;
    }

    // Map file to memory
    m_data = static_cast<char*>(mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0));

    if (m_data == MAP_FAILED) {
        close(m_fd);
        m_fd = -1;
        m_data = nullptr;
        err = "Could not map shared memory: " + std::string(strerror(errno));
        return;
    }

    m_size = size;
}

BufferObject::~BufferObject()
{
    if (m_data != nullptr && m_data != MAP_FAILED) {
        munmap(m_data, m_size);
        m_data = nullptr;
    }

    if (m_fd >= 0) {
        close(m_fd);
        m_fd = -1;
    }
}

#endif

error mapBuffer(const std::string& name, int size, Buffer& b)
{
    if (name.empty() || size <= 0) {
        return "Invalid buffer parameters";
    }

    error err;
    b.reset(new BufferObject(name, size, err));
    if (!err.empty()) {
        b.reset();
    }
    return err;
}

// Shared BufferObject collection
class buffers {
public:
    // Load loads shared BufferObject data and increments the client reference
    Buffer Load(const std::string& handle, int size)
    {
        if (handle.empty() || size <= 0) {
            return nullptr;
        }

        std::lock_guard<std::mutex> lock(m_mutex);

        // Load shared BufferObject data
        if (m_bufferMap.find(handle) == m_bufferMap.end()) {
            Buffer b;
            error err = mapBuffer(handle, size, b);
            if (!err.empty() || !b) {
                return nullptr;
            }
            m_bufferMap[handle] = b;
        }

        // Increment BufferObject ref
        m_bufferMap[handle]->incrementRef();

        // Done
        return m_bufferMap[handle];
    }

    // Delete deletes the client shared BufferObject references
    void Delete(const std::string& handle)
    {
        if (handle.empty()) {
            return;
        }

        std::lock_guard<std::mutex> lock(m_mutex);

        // Free shared buffer
        auto it = m_bufferMap.find(handle);
        if (it != m_bufferMap.end()) {
            int remainingRefs = it->second->decrementRef();
            if (remainingRefs <= 0) {
                m_bufferMap.erase(it);
            }
        }
    }

private:
    std::map<std::string, Buffer> m_bufferMap;
    std::mutex m_mutex;
};


//---------------------------------------------------------------------------
// pipeConn class
//---------------------------------------------------------------------------

class pipeConn {
public:
    pipeConn();
    ~pipeConn();
    error dial(const std::string& addr);
    error Read(char* p, int l, int& n);
    error Write(const char* p, int l, int& n);
    error Close();
    bool isConnected() const;

private:
#ifdef _WIN32
    HANDLE m_handle;
#else
    SOCKET m_sock;
#endif

    // No copy allowed
    pipeConn(const pipeConn&) = delete;
    pipeConn& operator=(const pipeConn&) = delete;
};


#ifdef _WIN32

pipeConn::pipeConn() : m_handle(INVALID_HANDLE_VALUE) {}

pipeConn::~pipeConn()
{
    Close();
}

error pipeConn::dial(const std::string& addr)
{
    if (addr.empty()) {
        return "Invalid pipe address";
    }

    Close(); // Close any existing connection

    const std::string path = "\\\\.\\pipe\\" + addr;
    std::wstring widePath(path.begin(), path.end());

    m_handle = CreateFileW(
        widePath.c_str(),
        GENERIC_READ | GENERIC_WRITE, // read and write access
        0, // no sharing
        nullptr, // default security attributes
        OPEN_EXISTING, // opens existing pipe
        0, // default attributes
        nullptr // no template file
    );

    if (m_handle == INVALID_HANDLE_VALUE) {
        return "Could not open named pipe: " + getLastErrorString();
    }
    return "";
}

error pipeConn::Read(char* p, int l, int& n)
{
    if (!p || l <= 0) {
        n = 0;
        return "Invalid read buffer parameters";
    }

    if (m_handle == INVALID_HANDLE_VALUE) {
        n = 0;
        return "Pipe not connected";
    }

    DWORD bytesRead = 0;
    if (!ReadFile(
            m_handle, // pipe handle
            p, // buffer to receive data
            static_cast<DWORD>(l), // size of buffer
            &bytesRead, // number of bytes read
            nullptr)) // not overlapped
    {
        n = 0;
        return "ReadFile from pipe failed: " + getLastErrorString();
    }

    n = static_cast<int>(bytesRead);
    return "";
}

error pipeConn::Write(const char* p, int l, int& n)
{
    if (!p || l <= 0) {
        n = 0;
        return "Invalid write buffer parameters";
    }

    if (m_handle == INVALID_HANDLE_VALUE) {
        n = 0;
        return "Pipe not connected";
    }

    DWORD bytesWritten = 0;
    if (!WriteFile(
            m_handle, // pipe handle
            p, // message
            static_cast<DWORD>(l), // message length
            &bytesWritten, // bytes written
            nullptr)) // not overlapped
    {
        n = 0;
        return "WriteFile to pipe failed: " + getLastErrorString();
    }

    n = static_cast<int>(bytesWritten);
    return "";
}

error pipeConn::Close()
{
    if (m_handle != INVALID_HANDLE_VALUE) {
        if (!CloseHandle(m_handle)) {
            m_handle = INVALID_HANDLE_VALUE;
            return "Failed to close pipe handle: " + getLastErrorString();
        }
        m_handle = INVALID_HANDLE_VALUE;
    }
    return "";
}

bool pipeConn::isConnected() const
{
    return m_handle != INVALID_HANDLE_VALUE;
}

#else // defined(_WIN32)

pipeConn::pipeConn() : m_sock(-1) {}

pipeConn::~pipeConn()
{
    Close();
}

error pipeConn::dial(const std::string& addr)
{
    if (addr.empty()) {
        return "Invalid socket addr";
    }

    Close(); // Close any existing connection

    // Create filesystem path for unix socket
    std::string p;
    try {
        p = std::filesystem::temp_directory_path().string();
    } catch (const std::filesystem::filesystem_error& e) {
        return std::string("Failed to get temp directory: ") + e.what();
    }

    const std::string socket_path = p + "/" + addr + ".sock";

    // Create unix socket
    m_sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (m_sock == -1) {
        return std::string("Unable to create socket: ") + strerror(errno);
    }

    struct sockaddr_un remote;
    remote.sun_family = AF_UNIX;

    // Check for path length overflow
    if (socket_path.length() >= sizeof(remote.sun_path)) {
        Close();
        return "Socket path too long";
    }

    strcpy(remote.sun_path, socket_path.c_str());
    socklen_t data_len = strlen(remote.sun_path) + sizeof(remote.sun_family);

    // Connect to server address
    if (connect(m_sock, reinterpret_cast<struct sockaddr*>(&remote), data_len) == -1) {
        const std::string errorMsg = std::string("Connection failed: ") + strerror(errno);
        Close();
        return errorMsg;
    }

    return "";
}

error pipeConn::Read(char* p, int l, int& n)
{
    if (!p || l <= 0) {
        n = 0;
        return "Invalid read buffer parameters";
    }

    if (m_sock < 0) {
        n = 0;
        return "Socket not connected";
    }

    n = recv(m_sock, p, l, 0);
    if (n == -1) {
        n = 0;
        return std::string("recv() failed: ") + strerror(errno);
    }

    return "";
}

error pipeConn::Write(const char* p, int l, int& n)
{
    if (!p || l <= 0) {
        n = 0;
        return "Invalid write buffer parameters";
    }

    if (m_sock < 0) {
        n = 0;
        return "Socket not connected";
    }

    n = send(m_sock, p, l, 0);
    if (n == -1) {
        n = 0;
        return std::string("send() failed: ") + strerror(errno);
    }

    return "";
}

error pipeConn::Close()
{
    if (m_sock >= 0) {
        // First try to properly shutdown the socket
        if (shutdown(m_sock, SHUT_RDWR) == -1 && errno != ENOTCONN) {
            // Non-fatal error, continue closing
        }

        if (close(m_sock) == -1) {
            m_sock = -1;
            return std::string("close() failed: ") + strerror(errno);
        }

        m_sock = -1;
    }
    return "";
}

bool pipeConn::isConnected() const
{
    return m_sock >= 0;
}

#endif // _WIN32


//----------------------------------------------------------------------------
// RoundTripperObject class
//----------------------------------------------------------------------------

class RoundTripperObject {
public:
    RoundTripperObject();
    ~RoundTripperObject();

    error call(const std::string& method, const std::string& url, const json& body, json& response);

private:
    error openConnection(const std::string& addr);
    error receiveAndDecodeResponse(json& j);
    error encodeAndSendRequest(const json& j);

    std::string m_scheme;
    pipeConn m_conn;
    std::vector<char> m_connBuffer;
    std::mutex m_mutex;

    RoundTripperObject(const RoundTripperObject&) = delete;
    RoundTripperObject& operator=(const RoundTripperObject&) = delete;
};

RoundTripperObject::RoundTripperObject()
    : m_scheme("")
    , m_connBuffer(1024 * 1024 * 16, 0) // 16MB buffer
{
}

RoundTripperObject::~RoundTripperObject()
{
    std::lock_guard<std::mutex> lock(m_mutex);
    m_scheme = "";
    m_conn.Close();
}

error RoundTripperObject::call(const std::string& method, const std::string& url, const json& body, json& response)
{
    if (url.empty()) {
        return "Empty URL";
    }

    if (method.empty()) {
        return "Empty method";
    }

    std::lock_guard<std::mutex> lock(m_mutex);

    // Validate URL
    const size_t schemeEndPos = url.find(':');
    if (schemeEndPos == std::string::npos) {
        return "URL scheme not found";
    }

    std::string urlScheme = url.substr(0, schemeEndPos);
    if (urlScheme.empty()) {
        return "Empty URL scheme";
    }

    // Validate connection
    if (!m_scheme.empty() && m_scheme != urlScheme) {
        return "Bad URL scheme: " + m_scheme + " != " + urlScheme;
    }

    if (m_scheme.empty()) {
        error err = openConnection(urlScheme);
        if (!err.empty()) {
            return err;
        }
        m_scheme = urlScheme;
    }

    // JSON Request
    {
        json request = {
            { "method", method },
            { "url", url },
            { "body", body },
        };
        error err = encodeAndSendRequest(request);
        if (!err.empty()) {
            return err;
        }
    }

    // JSON Response
    {
        error err = receiveAndDecodeResponse(response);
        if (!err.empty()) {
            return err;
        }
    }

    // Check for error in response
    std::string err;
    std::string location;

    if (response.contains("error") && response["error"].is_string()) {
        err = response["error"].get<std::string>();
    }

    if (response.contains("location") && response["location"].is_string()) {
        location = response["location"].get<std::string>();
    }

    if (err == ErrRedirect && !location.empty()) {
        return "redirect: " + location;
    } else if (!err.empty()) {
        return err;
    }

    // Success
    return "";
}

error RoundTripperObject::openConnection(const std::string& addr) {
    return m_conn.dial(addr);
}

error RoundTripperObject::receiveAndDecodeResponse(json& j)
{
    int n = 0;
    error err = m_conn.Read(m_connBuffer.data(), static_cast<int>(m_connBuffer.size()), n);
    if (!err.empty()) {
        m_conn.Close();
        return "Read error: " + err;
    }

    if (n <= 0) {
        m_conn.Close();
        return "Empty response";
    }

    try {
        j = json::parse(std::string(m_connBuffer.data(), n));
    } catch (const json::exception& e) {
        m_conn.Close();
        return std::string("JSON parse error: ") + e.what();
    }

    return "";
}

error RoundTripperObject::encodeAndSendRequest(const json& j)
{
    int n = 0;
    std::string data;

    try {
        data = j.dump();
    } catch (const json::exception& e) {
        return std::string("JSON serialization error: ") + e.what();
    }

    error err = m_conn.Write(data.c_str(), static_cast<int>(data.size()), n);
    if (!err.empty()) {
        m_conn.Close();
        return "Write error: " + err;
    }

    if (n != static_cast<int>(data.size())) {
        m_conn.Close();
        return "Incomplete write: wrote " + std::to_string(n) + " of " + std::to_string(data.size()) + " bytes";
    }

    return "";
}


namespace {

// Background dispatcher that releases packet references without blocking the
// render loop. The C++ client used to execute DELETE requests synchronously in
// packet destructors, which meant frame presentation stalled on every network
// round-trip. The Go client defers the call; we mirror that behaviour here.
class PacketCleanupDispatcher {
public:
    static PacketCleanupDispatcher& Instance()
    {
        static PacketCleanupDispatcher* instance = []() {
            return new PacketCleanupDispatcher();
        }();
        return *instance;
    }

    void Enqueue(const RoundTripper& rt, std::string ref)
    {
        if (!rt || ref.empty()) {
            return;
        }

        {
            std::lock_guard<std::mutex> lock(m_mutex);
            m_queue.emplace(rt, std::move(ref));
        }
        m_cv.notify_one();
    }

private:
    PacketCleanupDispatcher()
        : m_running(true)
        , m_worker([this]() { WorkerLoop(); })
    {
    }

    ~PacketCleanupDispatcher()
    {
        m_running = false;
        m_cv.notify_all();
        if (m_worker.joinable()) {
            m_worker.join();
        }
    }

    PacketCleanupDispatcher(const PacketCleanupDispatcher&) = delete;
    PacketCleanupDispatcher& operator=(const PacketCleanupDispatcher&) = delete;

    void WorkerLoop()
    {
        std::unique_lock<std::mutex> lock(m_mutex);
        while (true) {
            m_cv.wait(lock, [this]() { return !m_queue.empty() || !m_running; });
            if (!m_running && m_queue.empty()) {
                return;
            }

            auto pending = std::move(m_queue.front());
            m_queue.pop();
            auto rt = pending.first;
            auto ref = std::move(pending.second);
            lock.unlock();

            if (rt && !ref.empty()) {
                try {
                    json response;
                    error err = rt->call("DELETE", ref, json(), response);
                    if (!err.empty()) {
                        std::cerr << "Error deleting packet reference: " << err << std::endl;
                    }
                } catch (const std::exception& ex) {
                    std::cerr << "Exception deleting packet reference: " << ex.what() << std::endl;
                }
            }

            lock.lock();
        }
    }

    std::mutex m_mutex;
    std::condition_variable m_cv;
    std::queue<std::pair<RoundTripper, std::string>> m_queue;
    std::atomic<bool> m_running;
    std::thread m_worker;
};

} // unnamed namespace


//---------------------------------------------------------------------------
// PacketObject class
//---------------------------------------------------------------------------
static buffers sharedBuffers;

PacketObject::PacketObject(const json& j, error& err)
{
    err.clear();

    // Parse fields
    if (!Load(j, "track", track)) {
        err = "Error loading 'track' field";
        return;
    }
    if (!Load(j, "media", media)) {
        err = "Error loading 'media' field";
        return;
    }
    if (!Load(j, "signal", signal)) {
        err = "Error loading 'signal' field";
        return;
    }
    if (!Load(j, "timestamp", timestamp)) {
        err = "Error loading 'timestamp' field";
        return;
    }
    Load(j, "meta", meta);

    // Decode base64 data field
    if (Load(j, "data", m_data)) {
        data = m_data.data();
        length = static_cast<int>(m_data.size());
        // Done
        return;
    }

    // Capture shared memory reference
    if (!Load(j, "ref", ref)) {
        // Done - no data or ref
        return;
    }

    // Load shared memory fields
    if (!Load(j, "handle", m_handle)) {
        err = "Error loading 'handle' field";
        return;
    }
    int len = 0, cap = 0;
    if (!Load(j, "len", len)) {
        err = "Error loading 'len' field";
        return;
    }
    if (!Load(j, "cap", cap)) {
        err = "Error loading 'cap' field";
        return;
    }

    // Validate values
    if (cap <= 0) {
        err = "Invalid capacity value";
        return;
    }
    if (len < 0 || len > cap) {
        err = "Invalid buffer boundaries";
        return;
    }

    // Load shared memory data
    m_buf = sharedBuffers.Load(m_handle, cap);
    if (!m_buf) {
        err = "Error loading shared memory 'handle': " + m_handle;
        return;
    };

    data = m_buf->getData();
    length = len;
}

PacketObject::~PacketObject()
{
    if (!m_handle.empty()) {
        sharedBuffers.Delete(m_handle);
    }

    if (!ref.empty() && m_roundTripper) {
        PacketCleanupDispatcher::Instance().Enqueue(m_roundTripper, ref);
    }

    // Explicitly reset members for clarity
    data = nullptr;
    length = 0;
}


// ---------------------------------------------------------------------------
// ClientObject class
// ---------------------------------------------------------------------------

Client::Client()
    : m_roundTripper(std::make_shared<RoundTripperObject>())
{
}

// GET request

error Client::call(const std::string& method, const std::string& url, const json& body, json& response)
{
    if (!m_roundTripper) {
        return "Client has no round tripper";
    }

    if (method.empty()) {
        return "Empty method";
    }

    if (url.empty()) {
        return "Empty URL";
    }

    return m_roundTripper->call(method, url, body, response);
}

error Client::Get(const std::string& url, json& response)
{
    return call("GET", url, json(), response);
}

error Client::Get(const std::string& url, std::nullptr_t)
{
    json r;
    return call("GET", url, json(), r);
}

error Client::Get(const std::string& url, Worker& worker)
{
    // Process request
    json r;
    error err = Get(url, r);
    if (!err.empty()) {
        return err;
    }

    // Parse response
    try {
        r.get_to(worker);
    } catch (json::exception& e) {
        return e.what();
    }
    // Attach client to packets
    for (auto packet : worker->packets) {
        if (!packet->ref.empty()) {
            packet->m_roundTripper = m_roundTripper;
        }
    }

    //  Done
    return "";
}

// POST request

error Client::Post(const std::string& url, const json& body, json& response)
{
    return call("POST", url, body, response);
};

error Client::Post(const std::string& url, const json& body, std::nullptr_t)
{
    json r;
    return call("POST", url, body, r);
};

error Client::Post(const std::string& url, std::nullptr_t, json& response)
{
    return call("POST", url, json(), response);
};

error Client::Post(const std::string& url, std::nullptr_t, std::nullptr_t)
{
    json r;
    return call("POST", url, json(), r);
};

// DELETE request

error Client::Delete(const std::string& url)
{
    json r;
    return call("DELETE", url, json(), r);
};

} // namespace lt