// message_bus.cpp - Inter-app message bus implementation
// Milestone 18: Kernel-mediated message passing between apps

#include "message_bus.h"
#include <lua.hpp>
#include <algorithm>

namespace mosis {

// ============================================================================
// MessageBus
// ============================================================================

MessageBus::MessageBus() = default;
MessageBus::~MessageBus() = default;

bool MessageBus::MatchesFilter(const Intent& intent, const IntentFilter& filter) const {
    // Action must match
    if (filter.action != intent.action) {
        return false;
    }

    // If no types specified, accept all
    if (filter.types.empty()) {
        return true;
    }

    // Check if intent type matches any filter type
    for (const auto& t : filter.types) {
        if (t == intent.type) {
            return true;
        }
    }

    return false;
}

bool MessageBus::ValidateIntent(const Intent& intent, std::string& error) const {
    if (intent.action.empty()) {
        error = "intent action is required";
        return false;
    }

    // Note: Data size is checked separately to return DataTooLarge error
    return true;
}

void MessageBus::RegisterReceiver(
    const std::string& app_id,
    const IntentFilter& filter,
    IntentHandler handler
) {
    std::lock_guard<std::mutex> lock(m_mutex);
    m_receivers.push_back({app_id, filter, std::move(handler)});
}

void MessageBus::UnregisterApp(const std::string& app_id) {
    std::lock_guard<std::mutex> lock(m_mutex);
    m_receivers.erase(
        std::remove_if(m_receivers.begin(), m_receivers.end(),
            [&app_id](const ReceiverEntry& e) { return e.app_id == app_id; }),
        m_receivers.end()
    );
}

MessageError MessageBus::Send(
    const std::string& sender_app,
    const std::string& target_app,
    const Intent& intent,
    std::string& error
) {
    std::lock_guard<std::mutex> lock(m_mutex);

    // Check if sender is blocked
    if (m_blocked_apps.count(sender_app)) {
        error = "sender app is blocked";
        return MessageError::SenderBlocked;
    }

    // Validate intent
    if (!ValidateIntent(intent, error)) {
        return MessageError::InvalidIntent;
    }

    // Check data size
    if (intent.data.size() > MAX_DATA_SIZE) {
        error = "intent data exceeds maximum size";
        return MessageError::DataTooLarge;
    }

    // Find target receiver
    bool found = false;
    for (const auto& entry : m_receivers) {
        if (entry.app_id == target_app && MatchesFilter(intent, entry.filter)) {
            // Create intent with sender info
            Intent deliveredIntent = intent;
            deliveredIntent.from_app = sender_app;

            // Deliver
            entry.handler(deliveredIntent);
            found = true;
            m_message_count++;
            break;
        }
    }

    if (!found) {
        error = "no receiver found for target app with matching action";
        return MessageError::ReceiverNotFound;
    }

    return MessageError::None;
}

MessageError MessageBus::Broadcast(
    const std::string& sender_app,
    const Intent& intent,
    std::string& error
) {
    std::lock_guard<std::mutex> lock(m_mutex);

    // Check if sender is blocked
    if (m_blocked_apps.count(sender_app)) {
        error = "sender app is blocked";
        return MessageError::SenderBlocked;
    }

    // Validate intent
    if (!ValidateIntent(intent, error)) {
        return MessageError::InvalidIntent;
    }

    // Check data size
    if (intent.data.size() > MAX_DATA_SIZE) {
        error = "intent data exceeds maximum size";
        return MessageError::DataTooLarge;
    }

    // Collect matching handlers (avoid holding lock during callbacks)
    std::vector<IntentHandler> handlers;
    for (const auto& entry : m_receivers) {
        // Skip blocked receivers
        if (m_blocked_apps.count(entry.app_id)) {
            continue;
        }

        if (MatchesFilter(intent, entry.filter)) {
            handlers.push_back(entry.handler);
        }
    }

    if (handlers.empty()) {
        error = "no receivers registered for action: " + intent.action;
        return MessageError::NoReceivers;
    }

    // Create intent with sender info
    Intent deliveredIntent = intent;
    deliveredIntent.from_app = sender_app;

    // Deliver to all matching handlers
    for (const auto& handler : handlers) {
        handler(deliveredIntent);
        m_message_count++;
    }

    return MessageError::None;
}

bool MessageBus::HasReceiverFor(const std::string& action) const {
    std::lock_guard<std::mutex> lock(m_mutex);

    for (const auto& entry : m_receivers) {
        if (entry.filter.action == action && !m_blocked_apps.count(entry.app_id)) {
            return true;
        }
    }

    return false;
}

std::vector<std::string> MessageBus::GetReceiversFor(const std::string& action) const {
    std::lock_guard<std::mutex> lock(m_mutex);

    std::vector<std::string> result;
    std::unordered_set<std::string> seen;

    for (const auto& entry : m_receivers) {
        if (entry.filter.action == action &&
            !m_blocked_apps.count(entry.app_id) &&
            !seen.count(entry.app_id)) {
            result.push_back(entry.app_id);
            seen.insert(entry.app_id);
        }
    }

    return result;
}

void MessageBus::SetAppPermission(const std::string& app_id, const std::string& permission, bool granted) {
    std::lock_guard<std::mutex> lock(m_mutex);

    if (granted) {
        m_permissions[app_id].insert(permission);
    } else {
        auto it = m_permissions.find(app_id);
        if (it != m_permissions.end()) {
            it->second.erase(permission);
        }
    }
}

bool MessageBus::HasPermission(const std::string& app_id, const std::string& permission) const {
    std::lock_guard<std::mutex> lock(m_mutex);

    auto it = m_permissions.find(app_id);
    if (it == m_permissions.end()) {
        return false;
    }

    return it->second.count(permission) > 0;
}

void MessageBus::BlockApp(const std::string& app_id) {
    std::lock_guard<std::mutex> lock(m_mutex);
    m_blocked_apps.insert(app_id);
}

void MessageBus::UnblockApp(const std::string& app_id) {
    std::lock_guard<std::mutex> lock(m_mutex);
    m_blocked_apps.erase(app_id);
}

bool MessageBus::IsBlocked(const std::string& app_id) const {
    std::lock_guard<std::mutex> lock(m_mutex);
    return m_blocked_apps.count(app_id) > 0;
}

void MessageBus::Clear() {
    std::lock_guard<std::mutex> lock(m_mutex);
    m_receivers.clear();
    m_permissions.clear();
    m_blocked_apps.clear();
    m_message_count = 0;
}

size_t MessageBus::GetReceiverCount() const {
    std::lock_guard<std::mutex> lock(m_mutex);
    return m_receivers.size();
}

// ============================================================================
// Lua API
// ============================================================================

// Helper to set a global in the real _G (bypassing any proxy)
static void SetGlobalInRealG(lua_State* L, const char* name) {
    // Stack: value to set as global (at -1)
    lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);

    // Check if _G has a metatable with __index (sandbox proxy pattern)
    if (lua_getmetatable(L, -1)) {
        lua_getfield(L, -1, "__index");
        if (lua_istable(L, -1)) {
            // Found proxy, set in the real _G (__index table)
            lua_pushvalue(L, -4);  // Copy value
            lua_setfield(L, -2, name);
            lua_pop(L, 4);  // Pop __index, metatable, _G, original value
            return;
        }
        lua_pop(L, 2);  // Pop nil/__index and metatable
    }

    // No proxy, set in _G directly
    lua_pushvalue(L, -2);  // Copy value
    lua_setfield(L, -2, name);
    lua_pop(L, 2);  // Pop _G and original value
}

static const char* MESSAGE_BUS_KEY = "mosis.message_bus";
static const char* INTENTS_APP_ID_KEY = "mosis.intents_app_id";

static MessageBus* GetMessageBus(lua_State* L) {
    lua_getfield(L, LUA_REGISTRYINDEX, MESSAGE_BUS_KEY);
    auto* bus = static_cast<MessageBus*>(lua_touserdata(L, -1));
    lua_pop(L, 1);
    return bus;
}

static std::string GetAppId(lua_State* L) {
    lua_getfield(L, LUA_REGISTRYINDEX, INTENTS_APP_ID_KEY);
    std::string app_id;
    if (lua_isstring(L, -1)) {
        app_id = lua_tostring(L, -1);
    }
    lua_pop(L, 1);
    return app_id;
}

// Read Intent from Lua table
static Intent ReadIntent(lua_State* L, int index) {
    Intent intent;

    if (lua_type(L, index) != LUA_TTABLE) {
        return intent;
    }

    lua_getfield(L, index, "action");
    if (lua_isstring(L, -1)) {
        intent.action = lua_tostring(L, -1);
    }
    lua_pop(L, 1);

    lua_getfield(L, index, "type");
    if (lua_isstring(L, -1)) {
        intent.type = lua_tostring(L, -1);
    }
    lua_pop(L, 1);

    lua_getfield(L, index, "data");
    if (lua_isstring(L, -1)) {
        intent.data = lua_tostring(L, -1);
    }
    lua_pop(L, 1);

    return intent;
}

// Push Intent as Lua table
static void PushIntent(lua_State* L, const Intent& intent) {
    lua_createtable(L, 0, 4);

    lua_pushstring(L, intent.action.c_str());
    lua_setfield(L, -2, "action");

    lua_pushstring(L, intent.type.c_str());
    lua_setfield(L, -2, "type");

    lua_pushstring(L, intent.data.c_str());
    lua_setfield(L, -2, "data");

    lua_pushstring(L, intent.from_app.c_str());
    lua_setfield(L, -2, "from");
}

// intents.on(action, handler, [options])
static int lua_intents_on(lua_State* L) {
    auto* bus = GetMessageBus(L);
    if (!bus) {
        return luaL_error(L, "message bus not available");
    }

    std::string app_id = GetAppId(L);
    if (app_id.empty()) {
        return luaL_error(L, "app id not set");
    }

    const char* action = luaL_checkstring(L, 1);
    luaL_checktype(L, 2, LUA_TFUNCTION);

    IntentFilter filter;
    filter.action = action;

    // Parse optional options table
    if (lua_gettop(L) >= 3 && lua_istable(L, 3)) {
        lua_getfield(L, 3, "types");
        if (lua_istable(L, -1)) {
            int len = static_cast<int>(lua_rawlen(L, -1));
            for (int i = 1; i <= len; i++) {
                lua_rawgeti(L, -1, i);
                if (lua_isstring(L, -1)) {
                    filter.types.push_back(lua_tostring(L, -1));
                }
                lua_pop(L, 1);
            }
        }
        lua_pop(L, 1);
    }

    // Store handler reference
    lua_pushvalue(L, 2);  // Copy handler function
    int handler_ref = luaL_ref(L, LUA_REGISTRYINDEX);

    // Register receiver with C++ callback that invokes Lua handler
    bus->RegisterReceiver(app_id, filter, [L, handler_ref](const Intent& intent) {
        // Get handler function
        lua_rawgeti(L, LUA_REGISTRYINDEX, handler_ref);

        // Push intent as argument
        PushIntent(L, intent);

        // Call handler(intent)
        if (lua_pcall(L, 1, 0, 0) != LUA_OK) {
            // Log error but don't propagate
            lua_pop(L, 1);
        }
    });

    return 0;
}

// intents.send(target_app, intent) -> success, error
static int lua_intents_send(lua_State* L) {
    auto* bus = GetMessageBus(L);
    if (!bus) {
        return luaL_error(L, "message bus not available");
    }

    std::string app_id = GetAppId(L);
    if (app_id.empty()) {
        return luaL_error(L, "app id not set");
    }

    const char* target = luaL_checkstring(L, 1);
    luaL_checktype(L, 2, LUA_TTABLE);

    Intent intent = ReadIntent(L, 2);

    std::string error;
    auto result = bus->Send(app_id, target, intent, error);

    if (result == MessageError::None) {
        lua_pushboolean(L, 1);
        lua_pushnil(L);
    } else {
        lua_pushboolean(L, 0);
        lua_pushstring(L, error.c_str());
    }

    return 2;
}

// intents.broadcast(intent) -> count
static int lua_intents_broadcast(lua_State* L) {
    auto* bus = GetMessageBus(L);
    if (!bus) {
        return luaL_error(L, "message bus not available");
    }

    std::string app_id = GetAppId(L);
    if (app_id.empty()) {
        return luaL_error(L, "app id not set");
    }

    luaL_checktype(L, 1, LUA_TTABLE);

    Intent intent = ReadIntent(L, 1);

    // Get receiver count before broadcast
    auto receivers = bus->GetReceiversFor(intent.action);

    std::string error;
    auto result = bus->Broadcast(app_id, intent, error);

    if (result == MessageError::None) {
        lua_pushinteger(L, static_cast<int>(receivers.size()));
    } else if (result == MessageError::NoReceivers) {
        lua_pushinteger(L, 0);
    } else {
        return luaL_error(L, "%s", error.c_str());
    }

    return 1;
}

// intents.hasReceiver(action) -> boolean
static int lua_intents_hasReceiver(lua_State* L) {
    auto* bus = GetMessageBus(L);
    if (!bus) {
        return luaL_error(L, "message bus not available");
    }

    const char* action = luaL_checkstring(L, 1);
    lua_pushboolean(L, bus->HasReceiverFor(action));
    return 1;
}

// intents.getReceivers(action) -> array of app IDs
static int lua_intents_getReceivers(lua_State* L) {
    auto* bus = GetMessageBus(L);
    if (!bus) {
        return luaL_error(L, "message bus not available");
    }

    const char* action = luaL_checkstring(L, 1);
    auto receivers = bus->GetReceiversFor(action);

    lua_createtable(L, static_cast<int>(receivers.size()), 0);
    for (size_t i = 0; i < receivers.size(); i++) {
        lua_pushstring(L, receivers[i].c_str());
        lua_rawseti(L, -2, static_cast<int>(i + 1));
    }

    return 1;
}

// intents.unregisterAll()
static int lua_intents_unregisterAll(lua_State* L) {
    auto* bus = GetMessageBus(L);
    if (!bus) {
        return luaL_error(L, "message bus not available");
    }

    std::string app_id = GetAppId(L);
    if (!app_id.empty()) {
        bus->UnregisterApp(app_id);
    }

    return 0;
}

void RegisterIntentsAPI(lua_State* L, MessageBus* bus, const std::string& app_id) {
    // Store bus pointer
    lua_pushlightuserdata(L, bus);
    lua_setfield(L, LUA_REGISTRYINDEX, MESSAGE_BUS_KEY);

    // Store app ID
    lua_pushstring(L, app_id.c_str());
    lua_setfield(L, LUA_REGISTRYINDEX, INTENTS_APP_ID_KEY);

    // Create intents table
    lua_createtable(L, 0, 6);

    lua_pushcfunction(L, lua_intents_on);
    lua_setfield(L, -2, "on");

    lua_pushcfunction(L, lua_intents_send);
    lua_setfield(L, -2, "send");

    lua_pushcfunction(L, lua_intents_broadcast);
    lua_setfield(L, -2, "broadcast");

    lua_pushcfunction(L, lua_intents_hasReceiver);
    lua_setfield(L, -2, "hasReceiver");

    lua_pushcfunction(L, lua_intents_getReceivers);
    lua_setfield(L, -2, "getReceivers");

    lua_pushcfunction(L, lua_intents_unregisterAll);
    lua_setfield(L, -2, "unregisterAll");

    // Set as global "intents" (bypassing sandbox proxy)
    SetGlobalInRealG(L, "intents");
}

} // namespace mosis
