// contacts_interface.cpp - Contacts interface implementation
// Milestone 17: Contact access with granular read/write permissions

#include "contacts_interface.h"
#include <mosis/sandbox/lua_sandbox.h>
#include <lua.hpp>
#include <algorithm>
#include <cctype>

namespace mosis {

// Helper: case-insensitive string search
static bool ContainsIgnoreCase(const std::string& haystack, const std::string& needle) {
    if (needle.empty()) return true;
    if (haystack.empty()) return false;

    auto it = std::search(
        haystack.begin(), haystack.end(),
        needle.begin(), needle.end(),
        [](char a, char b) {
            return std::tolower(static_cast<unsigned char>(a)) ==
                   std::tolower(static_cast<unsigned char>(b));
        }
    );
    return it != haystack.end();
}

// ============================================================================
// ContactsInterface
// ============================================================================

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

ContactsInterface::~ContactsInterface() = default;

bool ContactsInterface::MatchesSearch(const Contact& contact, const std::string& search) const {
    if (search.empty()) return true;

    return ContainsIgnoreCase(contact.name, search) ||
           ContainsIgnoreCase(contact.phone, search) ||
           ContainsIgnoreCase(contact.email, search);
}

std::vector<Contact> ContactsInterface::Query(const QueryOptions& options, std::string& error) {
    std::lock_guard<std::mutex> lock(m_mutex);

    if (!m_has_read_permission) {
        error = "permission denied: contacts.read required";
        return {};
    }

    std::vector<Contact> results;

    // Cap limit at MAX_RESULTS
    int limit = std::min(options.limit, MAX_RESULTS);
    if (limit <= 0) limit = 50;

    int offset = std::max(options.offset, 0);

    // Filter and paginate
    int skipped = 0;
    for (const auto& contact : m_mock_contacts) {
        if (MatchesSearch(contact, options.search)) {
            if (skipped < offset) {
                skipped++;
                continue;
            }
            results.push_back(contact);
            if (static_cast<int>(results.size()) >= limit) {
                break;
            }
        }
    }

    return results;
}

std::optional<Contact> ContactsInterface::GetById(int id, std::string& error) {
    std::lock_guard<std::mutex> lock(m_mutex);

    if (!m_has_read_permission) {
        error = "permission denied: contacts.read required";
        return std::nullopt;
    }

    for (const auto& contact : m_mock_contacts) {
        if (contact.id == id) {
            return contact;
        }
    }

    error = "contact not found";
    return std::nullopt;
}

int ContactsInterface::Add(const Contact& contact, std::string& error) {
    std::lock_guard<std::mutex> lock(m_mutex);

    if (!m_has_write_permission) {
        error = "permission denied: contacts.write required";
        return 0;
    }

    if (contact.name.empty()) {
        error = "contact name is required";
        return 0;
    }

    Contact newContact = contact;
    newContact.id = m_next_id++;

    m_mock_contacts.push_back(newContact);
    return newContact.id;
}

bool ContactsInterface::Update(const Contact& contact, std::string& error) {
    std::lock_guard<std::mutex> lock(m_mutex);

    if (!m_has_write_permission) {
        error = "permission denied: contacts.write required";
        return false;
    }

    if (contact.id <= 0) {
        error = "invalid contact id";
        return false;
    }

    for (auto& c : m_mock_contacts) {
        if (c.id == contact.id) {
            c.name = contact.name;
            c.phone = contact.phone;
            c.email = contact.email;
            return true;
        }
    }

    error = "contact not found";
    return false;
}

bool ContactsInterface::Delete(int id, std::string& error) {
    std::lock_guard<std::mutex> lock(m_mutex);

    if (!m_has_write_permission) {
        error = "permission denied: contacts.write required";
        return false;
    }

    auto it = std::find_if(m_mock_contacts.begin(), m_mock_contacts.end(),
        [id](const Contact& c) { return c.id == id; });

    if (it == m_mock_contacts.end()) {
        error = "contact not found";
        return false;
    }

    m_mock_contacts.erase(it);
    return true;
}

int ContactsInterface::GetCount(std::string& error) {
    std::lock_guard<std::mutex> lock(m_mutex);

    if (!m_has_read_permission) {
        error = "permission denied: contacts.read required";
        return -1;
    }

    return static_cast<int>(m_mock_contacts.size());
}

void ContactsInterface::SetMockContacts(const std::vector<Contact>& contacts) {
    std::lock_guard<std::mutex> lock(m_mutex);
    m_mock_contacts = contacts;

    // Update next ID to be higher than any existing ID
    m_next_id = 1;
    for (const auto& c : m_mock_contacts) {
        if (c.id >= m_next_id) {
            m_next_id = c.id + 1;
        }
    }
}

void ContactsInterface::ClearMockContacts() {
    std::lock_guard<std::mutex> lock(m_mutex);
    m_mock_contacts.clear();
    m_next_id = 1;
}

// ============================================================================
// 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* CONTACTS_INTERFACE_KEY = "mosis.contacts_interface";

static ContactsInterface* GetContactsInterface(lua_State* L) {
    lua_getfield(L, LUA_REGISTRYINDEX, CONTACTS_INTERFACE_KEY);
    auto* contacts = static_cast<ContactsInterface*>(lua_touserdata(L, -1));
    lua_pop(L, 1);
    return contacts;
}

// Push Contact as Lua table
static void PushContact(lua_State* L, const Contact& contact) {
    lua_createtable(L, 0, 4);

    lua_pushinteger(L, contact.id);
    lua_setfield(L, -2, "id");

    lua_pushstring(L, contact.name.c_str());
    lua_setfield(L, -2, "name");

    lua_pushstring(L, contact.phone.c_str());
    lua_setfield(L, -2, "phone");

    lua_pushstring(L, contact.email.c_str());
    lua_setfield(L, -2, "email");
}

// Read Contact from Lua table at given index
static Contact ReadContact(lua_State* L, int index) {
    Contact contact;

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

    lua_getfield(L, index, "id");
    if (lua_isinteger(L, -1)) {
        contact.id = static_cast<int>(lua_tointeger(L, -1));
    }
    lua_pop(L, 1);

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

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

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

    return contact;
}

// contacts.query(options) -> array of contacts
static int lua_contacts_query(lua_State* L) {
    auto* contacts = GetContactsInterface(L);
    if (!contacts) {
        return luaL_error(L, "contacts interface not available");
    }

    QueryOptions options;

    // Parse options table (optional)
    if (lua_gettop(L) >= 1 && lua_istable(L, 1)) {
        lua_getfield(L, 1, "search");
        if (lua_isstring(L, -1)) {
            options.search = lua_tostring(L, -1);
        }
        lua_pop(L, 1);

        lua_getfield(L, 1, "limit");
        if (lua_isinteger(L, -1)) {
            options.limit = static_cast<int>(lua_tointeger(L, -1));
        }
        lua_pop(L, 1);

        lua_getfield(L, 1, "offset");
        if (lua_isinteger(L, -1)) {
            options.offset = static_cast<int>(lua_tointeger(L, -1));
        }
        lua_pop(L, 1);
    }

    std::string error;
    auto results = contacts->Query(options, error);

    if (!error.empty()) {
        return luaL_error(L, "%s", error.c_str());
    }

    // Return array of contacts
    lua_createtable(L, static_cast<int>(results.size()), 0);
    for (size_t i = 0; i < results.size(); i++) {
        PushContact(L, results[i]);
        lua_rawseti(L, -2, static_cast<int>(i + 1));
    }

    return 1;
}

// contacts.getById(id) -> contact or nil
static int lua_contacts_getById(lua_State* L) {
    auto* contacts = GetContactsInterface(L);
    if (!contacts) {
        return luaL_error(L, "contacts interface not available");
    }

    int id = static_cast<int>(luaL_checkinteger(L, 1));

    std::string error;
    auto contact = contacts->GetById(id, error);

    if (!error.empty() && error != "contact not found") {
        return luaL_error(L, "%s", error.c_str());
    }

    if (contact) {
        PushContact(L, *contact);
    } else {
        lua_pushnil(L);
    }

    return 1;
}

// contacts.getCount() -> number
static int lua_contacts_getCount(lua_State* L) {
    auto* contacts = GetContactsInterface(L);
    if (!contacts) {
        return luaL_error(L, "contacts interface not available");
    }

    std::string error;
    int count = contacts->GetCount(error);

    if (!error.empty()) {
        return luaL_error(L, "%s", error.c_str());
    }

    lua_pushinteger(L, count);
    return 1;
}

// contacts.add(contact) -> id
static int lua_contacts_add(lua_State* L) {
    auto* contacts = GetContactsInterface(L);
    if (!contacts) {
        return luaL_error(L, "contacts interface not available");
    }

    luaL_checktype(L, 1, LUA_TTABLE);
    Contact contact = ReadContact(L, 1);

    std::string error;
    int id = contacts->Add(contact, error);

    if (!error.empty()) {
        return luaL_error(L, "%s", error.c_str());
    }

    lua_pushinteger(L, id);
    return 1;
}

// contacts.update(contact) -> boolean
static int lua_contacts_update(lua_State* L) {
    auto* contacts = GetContactsInterface(L);
    if (!contacts) {
        return luaL_error(L, "contacts interface not available");
    }

    luaL_checktype(L, 1, LUA_TTABLE);
    Contact contact = ReadContact(L, 1);

    std::string error;
    bool success = contacts->Update(contact, error);

    if (!error.empty() && !success) {
        return luaL_error(L, "%s", error.c_str());
    }

    lua_pushboolean(L, success);
    return 1;
}

// contacts.delete(id) -> boolean
static int lua_contacts_delete(lua_State* L) {
    auto* contacts = GetContactsInterface(L);
    if (!contacts) {
        return luaL_error(L, "contacts interface not available");
    }

    int id = static_cast<int>(luaL_checkinteger(L, 1));

    std::string error;
    bool success = contacts->Delete(id, error);

    if (!error.empty() && !success) {
        return luaL_error(L, "%s", error.c_str());
    }

    lua_pushboolean(L, success);
    return 1;
}

void RegisterContactsAPI(lua_State* L, ContactsInterface* contacts) {
    // Store interface pointer
    lua_pushlightuserdata(L, contacts);
    lua_setfield(L, LUA_REGISTRYINDEX, CONTACTS_INTERFACE_KEY);

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

    lua_pushcfunction(L, lua_contacts_query);
    lua_setfield(L, -2, "query");

    lua_pushcfunction(L, lua_contacts_getById);
    lua_setfield(L, -2, "getById");

    lua_pushcfunction(L, lua_contacts_getCount);
    lua_setfield(L, -2, "getCount");

    lua_pushcfunction(L, lua_contacts_add);
    lua_setfield(L, -2, "add");

    lua_pushcfunction(L, lua_contacts_update);
    lua_setfield(L, -2, "update");

    lua_pushcfunction(L, lua_contacts_delete);
    lua_setfield(L, -2, "delete");

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

} // namespace mosis
