// location_interface.cpp - Location interface implementation for Lua sandbox
// Milestone 14: Location access with privacy controls and precision levels

#include "location_interface.h"
#include <algorithm>
#include <cmath>

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

namespace mosis {

// ============================================================================
// LocationWatch Implementation
// ============================================================================

LocationWatch::LocationWatch(int id, const LocationOptions& options)
    : m_id(id)
    , m_options(options)
{
}

LocationWatch::~LocationWatch() {
    Stop();
}

void LocationWatch::Start() {
    m_active = true;
}

void LocationWatch::Stop() {
    m_active = false;
}

void LocationWatch::SimulatePosition(const Position& pos) {
    if (m_active && m_on_position) {
        m_on_position(pos);
    }
}

void LocationWatch::SimulateError(LocationError error, const std::string& message) {
    if (m_active && m_on_error) {
        m_on_error(error, message);
    }
}

// ============================================================================
// LocationInterface Implementation
// ============================================================================

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

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

Position LocationInterface::ApplyPrecision(const Position& pos) const {
    if (m_has_fine_permission) {
        return pos;  // Full precision
    }

    // Coarse: round to ~1km (0.01 degrees ≈ 1.1km)
    Position coarse = pos;
    coarse.latitude = std::round(pos.latitude * 100.0) / 100.0;
    coarse.longitude = std::round(pos.longitude * 100.0) / 100.0;
    coarse.accuracy = std::max(pos.accuracy, COARSE_ACCURACY_M);
    coarse.altitude = 0.0;
    coarse.altitude_accuracy = 0.0;
    coarse.heading = 0.0;
    coarse.speed = 0.0;
    return coarse;
}

bool LocationInterface::CheckRateLimit() {
    auto now = std::chrono::steady_clock::now();

    if (m_first_request) {
        m_first_request = false;
        m_last_request_time = now;
        return true;
    }

    auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
        now - m_last_request_time
    ).count();

    if (elapsed < RATE_LIMIT_MS) {
        return false;  // Rate limited
    }

    m_last_request_time = now;
    return true;
}

bool LocationInterface::GetCurrentPosition(
    const LocationOptions& options,
    std::function<void(const Position&)> success,
    std::function<void(LocationError, const std::string&)> error,
    std::string& out_error
) {
    // Check permission
    if (!m_has_fine_permission && !m_has_coarse_permission) {
        out_error = "Location permission denied";
        return false;
    }

    // Check rate limit
    if (!CheckRateLimit()) {
        out_error = "Location request rate limited (1 per second)";
        return false;
    }

    if (m_mock_mode) {
        // In mock mode, immediately return the mock position
        Position result = ApplyPrecision(m_mock_position);
        result.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::system_clock::now().time_since_epoch()
        ).count();
        if (success) {
            success(result);
        }
        return true;
    }

    // Real implementation would use platform APIs here
    out_error = "Real location not implemented";
    return false;
}

std::shared_ptr<LocationWatch> LocationInterface::WatchPosition(
    const LocationOptions& options,
    std::string& error
) {
    std::lock_guard<std::mutex> lock(m_mutex);

    // Check permission
    if (!m_has_fine_permission && !m_has_coarse_permission) {
        error = "Location permission denied";
        return nullptr;
    }

    // Clean up stopped watches first
    CleanupStoppedWatches();

    // Check watch limit
    if (m_watches.size() >= MAX_WATCHES) {
        error = "Maximum watch limit reached (5)";
        return nullptr;
    }

    // Create watch
    int id = m_next_watch_id++;
    auto watch = std::make_shared<LocationWatch>(id, options);
    watch->Start();

    m_watches[id] = watch;
    return watch;
}

void LocationInterface::ClearWatch(int watch_id) {
    std::lock_guard<std::mutex> lock(m_mutex);
    auto it = m_watches.find(watch_id);
    if (it != m_watches.end()) {
        it->second->Stop();
        m_watches.erase(it);
    }
}

void LocationInterface::ClearAllWatches() {
    std::lock_guard<std::mutex> lock(m_mutex);
    for (auto& [id, watch] : m_watches) {
        watch->Stop();
    }
    m_watches.clear();
}

size_t LocationInterface::GetActiveWatchCount() const {
    std::lock_guard<std::mutex> lock(m_mutex);
    size_t count = 0;
    for (const auto& [id, watch] : m_watches) {
        if (watch->IsActive()) {
            count++;
        }
    }
    return count;
}

void LocationInterface::Shutdown() {
    ClearAllWatches();
}

std::shared_ptr<LocationWatch> LocationInterface::GetWatch(int id) {
    std::lock_guard<std::mutex> lock(m_mutex);
    auto it = m_watches.find(id);
    if (it != m_watches.end()) {
        return it->second;
    }
    return nullptr;
}

void LocationInterface::CleanupStoppedWatches() {
    // Called with lock held
    for (auto it = m_watches.begin(); it != m_watches.end();) {
        if (!it->second->IsActive()) {
            it = m_watches.erase(it);
        } else {
            ++it;
        }
    }
}

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

// Weak reference to watch stored in userdata
struct LuaWatchRef {
    std::weak_ptr<LocationWatch> watch;
    LocationInterface* location;
};

static const char* WATCH_METATABLE = "mosis.LocationWatch";

static LuaWatchRef* GetWatchRef(lua_State* L, int idx) {
    return static_cast<LuaWatchRef*>(luaL_checkudata(L, idx, WATCH_METATABLE));
}

static std::shared_ptr<LocationWatch> GetWatch(lua_State* L, int idx) {
    auto ref = GetWatchRef(L, idx);
    return ref->watch.lock();
}

// Watch methods

static int LuaWatch_stop(lua_State* L) {
    auto ref = GetWatchRef(L, 1);
    auto watch = ref->watch.lock();
    if (watch && ref->location) {
        ref->location->ClearWatch(watch->GetId());
    }
    return 0;
}

static int LuaWatch_getId(lua_State* L) {
    auto watch = GetWatch(L, 1);
    if (watch) {
        lua_pushinteger(L, watch->GetId());
    } else {
        lua_pushinteger(L, 0);
    }
    return 1;
}

static int LuaWatch_isActive(lua_State* L) {
    auto watch = GetWatch(L, 1);
    lua_pushboolean(L, watch && watch->IsActive());
    return 1;
}

static int LuaWatch_gc(lua_State* L) {
    auto ref = GetWatchRef(L, 1);
    ref->~LuaWatchRef();
    return 0;
}

static const luaL_Reg watch_methods[] = {
    {"stop", LuaWatch_stop},
    {"getId", LuaWatch_getId},
    {"isActive", LuaWatch_isActive},
    {nullptr, nullptr}
};

static void CreateWatchMetatable(lua_State* L) {
    luaL_newmetatable(L, WATCH_METATABLE);

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

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

    lua_pop(L, 1);
}

static void PushWatch(lua_State* L, std::shared_ptr<LocationWatch> watch, LocationInterface* location) {
    auto ref = static_cast<LuaWatchRef*>(lua_newuserdata(L, sizeof(LuaWatchRef)));
    new (ref) LuaWatchRef{watch, location};
    luaL_setmetatable(L, WATCH_METATABLE);
}

static const char* LocationErrorToString(LocationError err) {
    switch (err) {
        case LocationError::PermissionDenied: return "PERMISSION_DENIED";
        case LocationError::PositionUnavailable: return "POSITION_UNAVAILABLE";
        case LocationError::Timeout: return "TIMEOUT";
        case LocationError::RateLimited: return "RATE_LIMITED";
        case LocationError::WatchLimitReached: return "WATCH_LIMIT_REACHED";
        default: return "UNKNOWN";
    }
}

// location.getCurrentPosition(successCallback, errorCallback, options)
static int LuaLocation_getCurrentPosition(lua_State* L) {
    auto location = static_cast<LocationInterface*>(lua_touserdata(L, lua_upvalueindex(1)));
    if (!location) {
        lua_pushnil(L);
        lua_pushstring(L, "Location interface not available");
        return 2;
    }

    // Validate callbacks
    if (!lua_isfunction(L, 1)) {
        return luaL_error(L, "First argument must be a success callback function");
    }
    if (!lua_isfunction(L, 2) && !lua_isnil(L, 2)) {
        return luaL_error(L, "Second argument must be an error callback function or nil");
    }

    // Parse options from third argument (optional table)
    LocationOptions options;
    if (lua_istable(L, 3)) {
        lua_getfield(L, 3, "enableHighAccuracy");
        if (lua_isboolean(L, -1)) {
            options.enable_high_accuracy = lua_toboolean(L, -1);
        }
        lua_pop(L, 1);

        lua_getfield(L, 3, "timeout");
        if (lua_isnumber(L, -1)) {
            options.timeout_ms = static_cast<int>(lua_tointeger(L, -1));
        }
        lua_pop(L, 1);

        lua_getfield(L, 3, "maxAge");
        if (lua_isnumber(L, -1)) {
            options.max_age_ms = static_cast<int>(lua_tointeger(L, -1));
        }
        lua_pop(L, 1);
    }

    // Store callbacks in registry for later use
    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 = location->GetCurrentPosition(
        options,
        [L, success_ref](const Position& pos) {
            // Call success callback
            lua_rawgeti(L, LUA_REGISTRYINDEX, success_ref);
            if (lua_isfunction(L, -1)) {
                // Create position table
                lua_newtable(L);
                lua_pushnumber(L, pos.latitude);
                lua_setfield(L, -2, "latitude");
                lua_pushnumber(L, pos.longitude);
                lua_setfield(L, -2, "longitude");
                lua_pushnumber(L, pos.accuracy);
                lua_setfield(L, -2, "accuracy");
                lua_pushnumber(L, pos.altitude);
                lua_setfield(L, -2, "altitude");
                lua_pushnumber(L, pos.altitude_accuracy);
                lua_setfield(L, -2, "altitudeAccuracy");
                lua_pushnumber(L, pos.heading);
                lua_setfield(L, -2, "heading");
                lua_pushnumber(L, pos.speed);
                lua_setfield(L, -2, "speed");
                lua_pushinteger(L, pos.timestamp);
                lua_setfield(L, -2, "timestamp");

                lua_pcall(L, 1, 0, 0);
            } else {
                lua_pop(L, 1);
            }
            luaL_unref(L, LUA_REGISTRYINDEX, success_ref);
        },
        [L, error_ref](LocationError 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, LocationErrorToString(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) {
        // Clean up refs on failure
        luaL_unref(L, LUA_REGISTRYINDEX, success_ref);
        if (error_ref != LUA_NOREF) {
            luaL_unref(L, LUA_REGISTRYINDEX, error_ref);
        }
        lua_pushnil(L);
        lua_pushstring(L, err.c_str());
        return 2;
    }

    lua_pushboolean(L, 1);
    return 1;
}

// location.watchPosition(successCallback, errorCallback, options)
static int LuaLocation_watchPosition(lua_State* L) {
    auto location = static_cast<LocationInterface*>(lua_touserdata(L, lua_upvalueindex(1)));
    if (!location) {
        lua_pushnil(L);
        lua_pushstring(L, "Location interface not available");
        return 2;
    }

    // Parse options from third argument (optional table)
    LocationOptions options;
    if (lua_istable(L, 3)) {
        lua_getfield(L, 3, "enableHighAccuracy");
        if (lua_isboolean(L, -1)) {
            options.enable_high_accuracy = lua_toboolean(L, -1);
        }
        lua_pop(L, 1);

        lua_getfield(L, 3, "timeout");
        if (lua_isnumber(L, -1)) {
            options.timeout_ms = static_cast<int>(lua_tointeger(L, -1));
        }
        lua_pop(L, 1);
    }

    std::string err;
    auto watch = location->WatchPosition(options, err);

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

    PushWatch(L, watch, location);
    return 1;
}

// location.clearWatch(watchId)
static int LuaLocation_clearWatch(lua_State* L) {
    auto location = static_cast<LocationInterface*>(lua_touserdata(L, lua_upvalueindex(1)));
    if (location) {
        int watch_id = static_cast<int>(luaL_checkinteger(L, 1));
        location->ClearWatch(watch_id);
    }
    return 0;
}

// location.getWatchCount()
static int LuaLocation_getWatchCount(lua_State* L) {
    auto location = static_cast<LocationInterface*>(lua_touserdata(L, lua_upvalueindex(1)));
    if (location) {
        lua_pushinteger(L, static_cast<lua_Integer>(location->GetActiveWatchCount()));
    } 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 RegisterLocationAPI(lua_State* L, LocationInterface* location) {
    // Create watch metatable
    CreateWatchMetatable(L);

    // Create location table
    lua_newtable(L);

    // location.getCurrentPosition
    lua_pushlightuserdata(L, location);
    lua_pushcclosure(L, LuaLocation_getCurrentPosition, 1);
    lua_setfield(L, -2, "getCurrentPosition");

    // location.watchPosition
    lua_pushlightuserdata(L, location);
    lua_pushcclosure(L, LuaLocation_watchPosition, 1);
    lua_setfield(L, -2, "watchPosition");

    // location.clearWatch
    lua_pushlightuserdata(L, location);
    lua_pushcclosure(L, LuaLocation_clearWatch, 1);
    lua_setfield(L, -2, "clearWatch");

    // location.getWatchCount
    lua_pushlightuserdata(L, location);
    lua_pushcclosure(L, LuaLocation_getWatchCount, 1);
    lua_setfield(L, -2, "getWatchCount");

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

} // namespace mosis
