// bluetooth_interface.cpp - Bluetooth interface implementation for Lua sandbox
// Milestone 16: Bluetooth discovery and pairing with user consent

#include "bluetooth_interface.h"
#include <algorithm>

extern "C" {
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}

namespace mosis {

// ============================================================================
// BluetoothConnection Implementation
// ============================================================================

BluetoothConnection::BluetoothConnection(int id, const std::string& address)
    : m_id(id)
    , m_address(address)
{
    m_connected = true;  // In mock mode, connections are immediately "connected"
}

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

bool BluetoothConnection::Send(const std::vector<uint8_t>& data, std::string& error) {
    if (!m_connected) {
        error = "Not connected";
        return false;
    }

    // In mock mode, send always succeeds
    // Real implementation would send via BluetoothSocket
    return true;
}

void BluetoothConnection::Close() {
    bool was_connected = m_connected.exchange(false);
    if (was_connected && m_on_disconnect) {
        m_on_disconnect();
    }
}

void BluetoothConnection::SimulateData(const std::vector<uint8_t>& data) {
    if (m_connected && m_on_data) {
        m_on_data(data);
    }
}

void BluetoothConnection::SimulateDisconnect() {
    Close();
}

// ============================================================================
// BluetoothInterface Implementation
// ============================================================================

BluetoothInterface::BluetoothInterface(const std::string& app_id)
    : m_app_id(app_id)
{
}

BluetoothInterface::~BluetoothInterface() {
    Shutdown();
}

bool BluetoothInterface::StartDiscovery(
    const DiscoveryOptions& options,
    std::function<void(const std::vector<BluetoothDevice>&)> success,
    std::function<void(BluetoothError, const std::string&)> error,
    std::string& out_error
) {
    // Check permission
    if (!m_has_bluetooth_permission) {
        out_error = "Bluetooth permission denied";
        return false;
    }

    // Check user consent (required for discovery)
    if (!m_has_user_consent) {
        out_error = "User consent required for Bluetooth discovery (user gesture needed)";
        return false;
    }

    // Check if already discovering
    bool expected = false;
    if (!m_is_discovering.compare_exchange_strong(expected, true)) {
        out_error = "Discovery already in progress";
        return false;
    }

    if (m_mock_mode) {
        // In mock mode, immediately return mock devices
        m_is_discovering = false;
        if (success) {
            success(m_mock_devices);
        }
        return true;
    }

    // Real implementation would use BluetoothAdapter via JNI
    m_is_discovering = false;
    out_error = "Real Bluetooth not implemented";
    return false;
}

void BluetoothInterface::StopDiscovery() {
    m_is_discovering = false;
    // Real implementation would cancel BluetoothAdapter discovery
}

std::shared_ptr<BluetoothConnection> BluetoothInterface::Connect(
    const std::string& address,
    const ConnectionOptions& options,
    std::string& error
) {
    std::lock_guard<std::mutex> lock(m_mutex);

    // Check permission
    if (!m_has_bluetooth_permission) {
        error = "Bluetooth permission denied";
        return nullptr;
    }

    // Clean up closed connections first
    CleanupClosedConnections();

    // Check connection limit
    if (m_connections.size() >= MAX_CONNECTIONS) {
        error = "Maximum connection limit reached (5)";
        return nullptr;
    }

    // Create connection
    int id = m_next_connection_id++;
    auto connection = std::make_shared<BluetoothConnection>(id, address);

    m_connections[id] = connection;
    return connection;
}

void BluetoothInterface::Disconnect(int connection_id) {
    std::lock_guard<std::mutex> lock(m_mutex);
    auto it = m_connections.find(connection_id);
    if (it != m_connections.end()) {
        it->second->Close();
        m_connections.erase(it);
    }
}

void BluetoothInterface::DisconnectAll() {
    std::lock_guard<std::mutex> lock(m_mutex);
    for (auto& [id, connection] : m_connections) {
        connection->Close();
    }
    m_connections.clear();
}

size_t BluetoothInterface::GetActiveConnectionCount() const {
    std::lock_guard<std::mutex> lock(m_mutex);
    size_t count = 0;
    for (const auto& [id, connection] : m_connections) {
        if (connection->IsConnected()) {
            count++;
        }
    }
    return count;
}

void BluetoothInterface::Shutdown() {
    StopDiscovery();
    DisconnectAll();
}

std::shared_ptr<BluetoothConnection> BluetoothInterface::GetConnection(int id) {
    std::lock_guard<std::mutex> lock(m_mutex);
    auto it = m_connections.find(id);
    if (it != m_connections.end()) {
        return it->second;
    }
    return nullptr;
}

void BluetoothInterface::CleanupClosedConnections() {
    // Called with lock held
    for (auto it = m_connections.begin(); it != m_connections.end();) {
        if (!it->second->IsConnected()) {
            it = m_connections.erase(it);
        } else {
            ++it;
        }
    }
}

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

// Weak reference to connection stored in userdata
struct LuaConnectionRef {
    std::weak_ptr<BluetoothConnection> connection;
    BluetoothInterface* bluetooth;
};

static const char* CONNECTION_METATABLE = "mosis.BluetoothConnection";

static LuaConnectionRef* GetConnectionRef(lua_State* L, int idx) {
    return static_cast<LuaConnectionRef*>(luaL_checkudata(L, idx, CONNECTION_METATABLE));
}

static std::shared_ptr<BluetoothConnection> GetConnection(lua_State* L, int idx) {
    auto ref = GetConnectionRef(L, idx);
    return ref->connection.lock();
}

// Connection methods

static int LuaConnection_close(lua_State* L) {
    auto ref = GetConnectionRef(L, 1);
    auto connection = ref->connection.lock();
    if (connection && ref->bluetooth) {
        ref->bluetooth->Disconnect(connection->GetId());
    }
    return 0;
}

static int LuaConnection_getId(lua_State* L) {
    auto connection = GetConnection(L, 1);
    if (connection) {
        lua_pushinteger(L, connection->GetId());
    } else {
        lua_pushinteger(L, 0);
    }
    return 1;
}

static int LuaConnection_getAddress(lua_State* L) {
    auto connection = GetConnection(L, 1);
    if (connection) {
        lua_pushstring(L, connection->GetAddress().c_str());
    } else {
        lua_pushstring(L, "");
    }
    return 1;
}

static int LuaConnection_isConnected(lua_State* L) {
    auto connection = GetConnection(L, 1);
    lua_pushboolean(L, connection && connection->IsConnected());
    return 1;
}

static int LuaConnection_send(lua_State* L) {
    auto connection = GetConnection(L, 1);
    if (!connection) {
        lua_pushboolean(L, 0);
        lua_pushstring(L, "Connection closed");
        return 2;
    }

    // Get data from table argument
    if (!lua_istable(L, 2)) {
        return luaL_error(L, "send() requires a table of bytes");
    }

    std::vector<uint8_t> data;
    lua_pushnil(L);
    while (lua_next(L, 2) != 0) {
        if (lua_isnumber(L, -1)) {
            data.push_back(static_cast<uint8_t>(lua_tointeger(L, -1)));
        }
        lua_pop(L, 1);
    }

    std::string error;
    bool ok = connection->Send(data, error);
    if (!ok) {
        lua_pushboolean(L, 0);
        lua_pushstring(L, error.c_str());
        return 2;
    }

    lua_pushboolean(L, 1);
    return 1;
}

static int LuaConnection_gc(lua_State* L) {
    auto ref = GetConnectionRef(L, 1);
    ref->~LuaConnectionRef();
    return 0;
}

static const luaL_Reg connection_methods[] = {
    {"close", LuaConnection_close},
    {"getId", LuaConnection_getId},
    {"getAddress", LuaConnection_getAddress},
    {"isConnected", LuaConnection_isConnected},
    {"send", LuaConnection_send},
    {nullptr, nullptr}
};

static void CreateConnectionMetatable(lua_State* L) {
    luaL_newmetatable(L, CONNECTION_METATABLE);

    // __index = methods table
    lua_newtable(L);
    luaL_setfuncs(L, connection_methods, 0);
    lua_setfield(L, -2, "__index");

    // __gc
    lua_pushcfunction(L, LuaConnection_gc);
    lua_setfield(L, -2, "__gc");

    lua_pop(L, 1);
}

static void PushConnection(lua_State* L, std::shared_ptr<BluetoothConnection> connection, BluetoothInterface* bluetooth) {
    auto ref = static_cast<LuaConnectionRef*>(lua_newuserdata(L, sizeof(LuaConnectionRef)));
    new (ref) LuaConnectionRef{connection, bluetooth};
    luaL_setmetatable(L, CONNECTION_METATABLE);
}

static const char* BluetoothErrorToString(BluetoothError err) {
    switch (err) {
        case BluetoothError::PermissionDenied: return "PERMISSION_DENIED";
        case BluetoothError::UserConsentRequired: return "USER_CONSENT_REQUIRED";
        case BluetoothError::DiscoveryFailed: return "DISCOVERY_FAILED";
        case BluetoothError::ConnectionFailed: return "CONNECTION_FAILED";
        case BluetoothError::NotConnected: return "NOT_CONNECTED";
        case BluetoothError::ConnectionLimitReached: return "CONNECTION_LIMIT";
        case BluetoothError::Timeout: return "TIMEOUT";
        case BluetoothError::AlreadyDiscovering: return "ALREADY_DISCOVERING";
        default: return "UNKNOWN";
    }
}

// bluetooth.startDiscovery(successCallback, errorCallback, options)
static int LuaBluetooth_startDiscovery(lua_State* L) {
    auto bluetooth = static_cast<BluetoothInterface*>(lua_touserdata(L, lua_upvalueindex(1)));
    if (!bluetooth) {
        lua_pushboolean(L, 0);
        lua_pushstring(L, "Bluetooth interface not available");
        return 2;
    }

    // Validate callbacks
    if (!lua_isfunction(L, 1)) {
        return luaL_error(L, "First argument must be a success callback function");
    }

    // Parse options from third argument (optional table)
    DiscoveryOptions options;
    if (lua_istable(L, 3)) {
        lua_getfield(L, 3, "timeout");
        if (lua_isnumber(L, -1)) {
            options.timeout_seconds = static_cast<int>(lua_tointeger(L, -1));
        }
        lua_pop(L, 1);
    }

    // Store callbacks in registry
    lua_pushvalue(L, 1);  // success callback
    int success_ref = luaL_ref(L, LUA_REGISTRYINDEX);

    int error_ref = LUA_NOREF;
    if (lua_isfunction(L, 2)) {
        lua_pushvalue(L, 2);  // error callback
        error_ref = luaL_ref(L, LUA_REGISTRYINDEX);
    }

    std::string err;
    bool ok = bluetooth->StartDiscovery(
        options,
        [L, success_ref](const std::vector<BluetoothDevice>& devices) {
            lua_rawgeti(L, LUA_REGISTRYINDEX, success_ref);
            if (lua_isfunction(L, -1)) {
                // Create devices array
                lua_newtable(L);
                for (size_t i = 0; i < devices.size(); i++) {
                    lua_newtable(L);
                    lua_pushstring(L, devices[i].name.c_str());
                    lua_setfield(L, -2, "name");
                    lua_pushstring(L, devices[i].address.c_str());
                    lua_setfield(L, -2, "address");
                    lua_rawseti(L, -2, static_cast<lua_Integer>(i + 1));
                }
                lua_pcall(L, 1, 0, 0);
            } else {
                lua_pop(L, 1);
            }
            luaL_unref(L, LUA_REGISTRYINDEX, success_ref);
        },
        [L, error_ref](BluetoothError error, const std::string& message) {
            if (error_ref != LUA_NOREF) {
                lua_rawgeti(L, LUA_REGISTRYINDEX, error_ref);
                if (lua_isfunction(L, -1)) {
                    lua_pushstring(L, BluetoothErrorToString(error));
                    lua_pushstring(L, message.c_str());
                    lua_pcall(L, 2, 0, 0);
                } else {
                    lua_pop(L, 1);
                }
                luaL_unref(L, LUA_REGISTRYINDEX, error_ref);
            }
        },
        err
    );

    if (!ok) {
        luaL_unref(L, LUA_REGISTRYINDEX, success_ref);
        if (error_ref != LUA_NOREF) {
            luaL_unref(L, LUA_REGISTRYINDEX, error_ref);
        }
        lua_pushboolean(L, 0);
        lua_pushstring(L, err.c_str());
        return 2;
    }

    lua_pushboolean(L, 1);
    return 1;
}

// bluetooth.stopDiscovery()
static int LuaBluetooth_stopDiscovery(lua_State* L) {
    auto bluetooth = static_cast<BluetoothInterface*>(lua_touserdata(L, lua_upvalueindex(1)));
    if (bluetooth) {
        bluetooth->StopDiscovery();
    }
    return 0;
}

// bluetooth.isDiscovering()
static int LuaBluetooth_isDiscovering(lua_State* L) {
    auto bluetooth = static_cast<BluetoothInterface*>(lua_touserdata(L, lua_upvalueindex(1)));
    lua_pushboolean(L, bluetooth && bluetooth->IsDiscovering());
    return 1;
}

// bluetooth.connect(address, onDataCallback, onErrorCallback, options)
static int LuaBluetooth_connect(lua_State* L) {
    auto bluetooth = static_cast<BluetoothInterface*>(lua_touserdata(L, lua_upvalueindex(1)));
    if (!bluetooth) {
        lua_pushnil(L);
        lua_pushstring(L, "Bluetooth interface not available");
        return 2;
    }

    // Get address
    const char* address = luaL_checkstring(L, 1);

    // Parse options from fourth argument (optional table)
    ConnectionOptions options;
    if (lua_istable(L, 4)) {
        lua_getfield(L, 4, "timeout");
        if (lua_isnumber(L, -1)) {
            options.timeout_ms = static_cast<int>(lua_tointeger(L, -1));
        }
        lua_pop(L, 1);
    }

    std::string err;
    auto connection = bluetooth->Connect(address, options, err);

    if (!connection) {
        lua_pushnil(L);
        lua_pushstring(L, err.c_str());
        return 2;
    }

    // Set up data callback if provided
    if (lua_isfunction(L, 2)) {
        lua_pushvalue(L, 2);
        int data_ref = luaL_ref(L, LUA_REGISTRYINDEX);
        connection->SetOnData([L, data_ref](const std::vector<uint8_t>& data) {
            lua_rawgeti(L, LUA_REGISTRYINDEX, data_ref);
            if (lua_isfunction(L, -1)) {
                // Create data table
                lua_newtable(L);
                for (size_t i = 0; i < data.size(); i++) {
                    lua_pushinteger(L, data[i]);
                    lua_rawseti(L, -2, static_cast<lua_Integer>(i + 1));
                }
                lua_pcall(L, 1, 0, 0);
            } else {
                lua_pop(L, 1);
            }
        });
    }

    // Set up error callback if provided
    if (lua_isfunction(L, 3)) {
        lua_pushvalue(L, 3);
        int error_ref = luaL_ref(L, LUA_REGISTRYINDEX);
        connection->SetOnError([L, error_ref](BluetoothError error, const std::string& message) {
            lua_rawgeti(L, LUA_REGISTRYINDEX, error_ref);
            if (lua_isfunction(L, -1)) {
                lua_pushstring(L, BluetoothErrorToString(error));
                lua_pushstring(L, message.c_str());
                lua_pcall(L, 2, 0, 0);
            } else {
                lua_pop(L, 1);
            }
        });
    }

    PushConnection(L, connection, bluetooth);
    return 1;
}

// bluetooth.disconnectAll()
static int LuaBluetooth_disconnectAll(lua_State* L) {
    auto bluetooth = static_cast<BluetoothInterface*>(lua_touserdata(L, lua_upvalueindex(1)));
    if (bluetooth) {
        bluetooth->DisconnectAll();
    }
    return 0;
}

// bluetooth.getConnectionCount()
static int LuaBluetooth_getConnectionCount(lua_State* L) {
    auto bluetooth = static_cast<BluetoothInterface*>(lua_touserdata(L, lua_upvalueindex(1)));
    if (bluetooth) {
        lua_pushinteger(L, static_cast<lua_Integer>(bluetooth->GetActiveConnectionCount()));
    } else {
        lua_pushinteger(L, 0);
    }
    return 1;
}

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

    if (lua_getmetatable(L, -1)) {
        lua_getfield(L, -1, "__index");
        if (lua_istable(L, -1)) {
            lua_pushvalue(L, -4);
            lua_setfield(L, -2, name);
            lua_pop(L, 4);
            return;
        }
        lua_pop(L, 2);
    }

    lua_pushvalue(L, -2);
    lua_setfield(L, -2, name);
    lua_pop(L, 2);
}

void RegisterBluetoothAPI(lua_State* L, BluetoothInterface* bluetooth) {
    // Create connection metatable
    CreateConnectionMetatable(L);

    // Create bluetooth table
    lua_newtable(L);

    // bluetooth.startDiscovery
    lua_pushlightuserdata(L, bluetooth);
    lua_pushcclosure(L, LuaBluetooth_startDiscovery, 1);
    lua_setfield(L, -2, "startDiscovery");

    // bluetooth.stopDiscovery
    lua_pushlightuserdata(L, bluetooth);
    lua_pushcclosure(L, LuaBluetooth_stopDiscovery, 1);
    lua_setfield(L, -2, "stopDiscovery");

    // bluetooth.isDiscovering
    lua_pushlightuserdata(L, bluetooth);
    lua_pushcclosure(L, LuaBluetooth_isDiscovering, 1);
    lua_setfield(L, -2, "isDiscovering");

    // bluetooth.connect
    lua_pushlightuserdata(L, bluetooth);
    lua_pushcclosure(L, LuaBluetooth_connect, 1);
    lua_setfield(L, -2, "connect");

    // bluetooth.disconnectAll
    lua_pushlightuserdata(L, bluetooth);
    lua_pushcclosure(L, LuaBluetooth_disconnectAll, 1);
    lua_setfield(L, -2, "disconnectAll");

    // bluetooth.getConnectionCount
    lua_pushlightuserdata(L, bluetooth);
    lua_pushcclosure(L, LuaBluetooth_getConnectionCount, 1);
    lua_setfield(L, -2, "getConnectionCount");

    // Set as global (bypasses sandbox proxy)
    SetGlobalInRealG(L, "bluetooth");
}

} // namespace mosis
