#include "timer_manager.h"

#include <lua.hpp>
#include <algorithm>
#ifdef __ANDROID__
#include <android/log.h>
#define TIMER_LOG(...) __android_log_print(ANDROID_LOG_INFO, "MosisOS", __VA_ARGS__)
#else
#include <cstdio>
#define TIMER_LOG(...) printf(__VA_ARGS__)
#endif

namespace mosis {

//=============================================================================
// CONSTRUCTOR / DESTRUCTOR
//=============================================================================

TimerManager::TimerManager() = default;

TimerManager::~TimerManager() {
    std::lock_guard<std::mutex> lock(m_mutex);

    // Release all Lua callback references
    for (auto& timer : m_timers) {
        if (timer.callback_ref != LUA_NOREF && timer.L) {
            luaL_unref(timer.L, LUA_REGISTRYINDEX, timer.callback_ref);
        }
    }
    m_timers.clear();
}

//=============================================================================
// TIMER CREATION
//=============================================================================

TimerId TimerManager::SetTimeout(lua_State* L, const std::string& app_id,
                                  int callback_ref, int delay_ms) {
    std::lock_guard<std::mutex> lock(m_mutex);

    // Check per-app limit
    if (m_app_timer_counts[app_id] >= MAX_TIMERS_PER_APP) {
        // Release the callback reference since we're not using it
        luaL_unref(L, LUA_REGISTRYINDEX, callback_ref);
        return 0;
    }

    // Clamp delay
    if (delay_ms < MIN_TIMEOUT_MS) {
        delay_ms = MIN_TIMEOUT_MS;
    }

    Timer timer;
    timer.id = m_next_id++;
    timer.app_id = app_id;
    timer.fire_time = std::chrono::steady_clock::now() + Duration(delay_ms);
    timer.interval = Duration(0);
    timer.callback_ref = callback_ref;
    timer.L = L;
    timer.cancelled = false;
    timer.is_interval = false;

    m_timers.push_back(timer);
    m_app_timer_counts[app_id]++;
    m_app_timer_ids[app_id].insert(timer.id);

    return timer.id;
}

TimerId TimerManager::SetInterval(lua_State* L, const std::string& app_id,
                                   int callback_ref, int interval_ms) {
    std::lock_guard<std::mutex> lock(m_mutex);

    TIMER_LOG("[Timer] SetInterval called: app=%s, interval=%dms, ref=%d",
              app_id.c_str(), interval_ms, callback_ref);

    // Check per-app limit
    if (m_app_timer_counts[app_id] >= MAX_TIMERS_PER_APP) {
        TIMER_LOG("[Timer] REJECTED: timer limit exceeded for %s", app_id.c_str());
        luaL_unref(L, LUA_REGISTRYINDEX, callback_ref);
        return 0;
    }

    // Clamp interval to minimum
    if (interval_ms < MIN_INTERVAL_MS) {
        interval_ms = MIN_INTERVAL_MS;
    }

    Timer timer;
    timer.id = m_next_id++;
    timer.app_id = app_id;
    timer.fire_time = std::chrono::steady_clock::now() + Duration(interval_ms);
    timer.interval = Duration(interval_ms);
    timer.callback_ref = callback_ref;
    timer.L = L;
    timer.cancelled = false;
    timer.is_interval = true;

    m_timers.push_back(timer);
    m_app_timer_counts[app_id]++;
    m_app_timer_ids[app_id].insert(timer.id);

    TIMER_LOG("[Timer] Created interval timer id=%llu for %s (total: %zu)",
              timer.id, app_id.c_str(), m_timers.size());

    return timer.id;
}

//=============================================================================
// TIMER CANCELLATION
//=============================================================================

bool TimerManager::ClearTimer(const std::string& app_id, TimerId id) {
    std::lock_guard<std::mutex> lock(m_mutex);

    // Find the timer
    auto it = std::find_if(m_timers.begin(), m_timers.end(),
        [id, &app_id](const Timer& t) {
            return t.id == id && t.app_id == app_id && !t.cancelled;
        });

    if (it == m_timers.end()) {
        return false;
    }

    // Mark as cancelled (will be removed during ProcessTimers)
    it->cancelled = true;

    // Release the Lua callback reference
    if (it->callback_ref != LUA_NOREF && it->L) {
        luaL_unref(it->L, LUA_REGISTRYINDEX, it->callback_ref);
        it->callback_ref = LUA_NOREF;
    }

    // Update counts
    if (m_app_timer_counts[app_id] > 0) {
        m_app_timer_counts[app_id]--;
    }
    m_app_timer_ids[app_id].erase(id);

    return true;
}

void TimerManager::ClearAppTimers(const std::string& app_id) {
    std::lock_guard<std::mutex> lock(m_mutex);

    // Get all timer IDs for this app
    auto it = m_app_timer_ids.find(app_id);
    if (it == m_app_timer_ids.end()) {
        return;
    }

    // Mark all timers as cancelled and release references
    for (auto& timer : m_timers) {
        if (timer.app_id == app_id && !timer.cancelled) {
            timer.cancelled = true;
            if (timer.callback_ref != LUA_NOREF && timer.L) {
                luaL_unref(timer.L, LUA_REGISTRYINDEX, timer.callback_ref);
                timer.callback_ref = LUA_NOREF;
            }
        }
    }

    // Clear tracking
    m_app_timer_counts[app_id] = 0;
    m_app_timer_ids[app_id].clear();
}

//=============================================================================
// TIMER PROCESSING
//=============================================================================

void TimerManager::FireTimer(Timer& timer) {
    if (timer.cancelled || timer.callback_ref == LUA_NOREF || !timer.L) {
        TIMER_LOG("[Timer] FireTimer skipped: cancelled=%d, ref=%d, L=%p",
                  timer.cancelled, timer.callback_ref, timer.L);
        return;
    }

    TIMER_LOG("[Timer] Firing timer id=%llu for %s", timer.id, timer.app_id.c_str());

    lua_State* L = timer.L;

    // Get the callback from registry
    lua_rawgeti(L, LUA_REGISTRYINDEX, timer.callback_ref);

    if (lua_isfunction(L, -1)) {
        // Call the callback with protected call
        int result = lua_pcall(L, 0, 0, 0);
        if (result != LUA_OK) {
            // Log the error
            const char* err = lua_tostring(L, -1);
            TIMER_LOG("[Timer] ERROR in callback: %s", err ? err : "(unknown)");
            lua_pop(L, 1);
        } else {
            TIMER_LOG("[Timer] Callback completed successfully");
        }
    } else {
        TIMER_LOG("[Timer] ERROR: callback ref is not a function!");
        lua_pop(L, 1);
    }
}

void TimerManager::RescheduleInterval(Timer& timer) {
    // Update fire time for next interval
    timer.fire_time = std::chrono::steady_clock::now() + timer.interval;
}

int TimerManager::ProcessTimers() {
    // We need to be careful here - firing a timer might cause
    // new timers to be added or timers to be cancelled

    std::vector<Timer> to_fire;
    std::vector<TimerId> to_reschedule;
    std::vector<TimerId> to_remove;

    auto now = std::chrono::steady_clock::now();

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

        // Find all timers that should fire
        for (auto& timer : m_timers) {
            if (timer.cancelled) {
                to_remove.push_back(timer.id);
            } else if (timer.fire_time <= now) {
                to_fire.push_back(timer);
                if (timer.is_interval) {
                    to_reschedule.push_back(timer.id);
                } else {
                    to_remove.push_back(timer.id);
                }
            }
        }

        // Log only when we have timers to fire (to avoid spam)
        if (!to_fire.empty()) {
            TIMER_LOG("[Timer] ProcessTimers: %zu ready to fire out of %zu total",
                      to_fire.size(), m_timers.size());
        }
    }

    // Fire timers outside the lock to allow callbacks to create new timers
    int fired_count = 0;
    for (auto& timer : to_fire) {
        FireTimer(timer);
        fired_count++;
    }

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

        // Reschedule intervals
        for (TimerId id : to_reschedule) {
            auto it = std::find_if(m_timers.begin(), m_timers.end(),
                [id](const Timer& t) { return t.id == id && !t.cancelled; });
            if (it != m_timers.end()) {
                RescheduleInterval(*it);
            }
        }

        // Remove completed/cancelled timers
        for (TimerId id : to_remove) {
            auto it = std::find_if(m_timers.begin(), m_timers.end(),
                [id](const Timer& t) { return t.id == id; });
            if (it != m_timers.end()) {
                // Release reference if not already released
                if (it->callback_ref != LUA_NOREF && it->L && !it->is_interval) {
                    luaL_unref(it->L, LUA_REGISTRYINDEX, it->callback_ref);
                }

                // Update counts only for non-cancelled (timeout) timers
                if (!it->cancelled && !it->is_interval) {
                    const std::string& app_id = it->app_id;
                    if (m_app_timer_counts[app_id] > 0) {
                        m_app_timer_counts[app_id]--;
                    }
                    m_app_timer_ids[app_id].erase(id);
                }

                m_timers.erase(it);
            }
        }
    }

    return fired_count;
}

size_t TimerManager::GetTimerCount(const std::string& app_id) const {
    std::lock_guard<std::mutex> lock(m_mutex);

    auto it = m_app_timer_counts.find(app_id);
    if (it == m_app_timer_counts.end()) {
        return 0;
    }
    return it->second;
}

//=============================================================================
// LUA API
//=============================================================================

// Registry keys for storing manager pointer and app_id
static const char* TIMER_MANAGER_KEY = "__mosis_timer_manager";
static const char* TIMER_APP_ID_KEY = "__mosis_timer_app_id";

// setTimeout(callback, delay_ms) -> timer_id
static int lua_setTimeout(lua_State* L) {
    // Check arguments
    luaL_checktype(L, 1, LUA_TFUNCTION);
    int delay_ms = static_cast<int>(luaL_checkinteger(L, 2));

    // Get timer manager from registry
    lua_getfield(L, LUA_REGISTRYINDEX, TIMER_MANAGER_KEY);
    if (!lua_islightuserdata(L, -1)) {
        return luaL_error(L, "timer system not initialized");
    }
    TimerManager* manager = static_cast<TimerManager*>(lua_touserdata(L, -1));
    lua_pop(L, 1);

    // Get app_id from registry
    lua_getfield(L, LUA_REGISTRYINDEX, TIMER_APP_ID_KEY);
    if (!lua_isstring(L, -1)) {
        return luaL_error(L, "app_id not set");
    }
    std::string app_id = lua_tostring(L, -1);
    lua_pop(L, 1);

    // Store the callback in registry
    lua_pushvalue(L, 1);  // Push the callback
    int callback_ref = luaL_ref(L, LUA_REGISTRYINDEX);

    // Create the timer
    TimerId id = manager->SetTimeout(L, app_id, callback_ref, delay_ms);

    if (id == 0) {
        return luaL_error(L, "timer limit exceeded");
    }

    lua_pushinteger(L, static_cast<lua_Integer>(id));
    return 1;
}

// clearTimeout(timer_id)
static int lua_clearTimeout(lua_State* L) {
    TimerId id = static_cast<TimerId>(luaL_checkinteger(L, 1));

    // Get timer manager from registry
    lua_getfield(L, LUA_REGISTRYINDEX, TIMER_MANAGER_KEY);
    if (!lua_islightuserdata(L, -1)) {
        return luaL_error(L, "timer system not initialized");
    }
    TimerManager* manager = static_cast<TimerManager*>(lua_touserdata(L, -1));
    lua_pop(L, 1);

    // Get app_id from registry
    lua_getfield(L, LUA_REGISTRYINDEX, TIMER_APP_ID_KEY);
    if (!lua_isstring(L, -1)) {
        return luaL_error(L, "app_id not set");
    }
    std::string app_id = lua_tostring(L, -1);
    lua_pop(L, 1);

    manager->ClearTimer(app_id, id);

    return 0;
}

// setInterval(callback, interval_ms) -> timer_id
static int lua_setInterval(lua_State* L) {
    // Check arguments
    luaL_checktype(L, 1, LUA_TFUNCTION);
    int interval_ms = static_cast<int>(luaL_checkinteger(L, 2));

    // Get timer manager from registry
    lua_getfield(L, LUA_REGISTRYINDEX, TIMER_MANAGER_KEY);
    if (!lua_islightuserdata(L, -1)) {
        return luaL_error(L, "timer system not initialized");
    }
    TimerManager* manager = static_cast<TimerManager*>(lua_touserdata(L, -1));
    lua_pop(L, 1);

    // Get app_id from registry
    lua_getfield(L, LUA_REGISTRYINDEX, TIMER_APP_ID_KEY);
    if (!lua_isstring(L, -1)) {
        return luaL_error(L, "app_id not set");
    }
    std::string app_id = lua_tostring(L, -1);
    lua_pop(L, 1);

    // Store the callback in registry
    lua_pushvalue(L, 1);  // Push the callback
    int callback_ref = luaL_ref(L, LUA_REGISTRYINDEX);

    // Create the timer
    TimerId id = manager->SetInterval(L, app_id, callback_ref, interval_ms);

    if (id == 0) {
        return luaL_error(L, "timer limit exceeded");
    }

    lua_pushinteger(L, static_cast<lua_Integer>(id));
    return 1;
}

// clearInterval(timer_id)
static int lua_clearInterval(lua_State* L) {
    // Same as clearTimeout
    return lua_clearTimeout(L);
}

void RegisterTimerAPI(lua_State* L, TimerManager* manager, const std::string& app_id) {
    // Store timer manager pointer in registry
    lua_pushlightuserdata(L, manager);
    lua_setfield(L, LUA_REGISTRYINDEX, TIMER_MANAGER_KEY);

    // Store app_id in registry
    lua_pushstring(L, app_id.c_str());
    lua_setfield(L, LUA_REGISTRYINDEX, TIMER_APP_ID_KEY);

    // Get the real _G (not the proxy)
    // We need to set these in the real global table that the proxy reads from
    lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);

    // Check if we're dealing with a proxy (has __index metatable)
    if (lua_getmetatable(L, -1)) {
        lua_getfield(L, -1, "__index");
        if (lua_istable(L, -1)) {
            // We have a proxy, use the __index table as the real _G
            lua_remove(L, -2);  // Remove metatable
            lua_remove(L, -2);  // Remove proxy

            // Now top of stack is real _G
            lua_pushcfunction(L, lua_setTimeout);
            lua_setfield(L, -2, "setTimeout");

            lua_pushcfunction(L, lua_clearTimeout);
            lua_setfield(L, -2, "clearTimeout");

            lua_pushcfunction(L, lua_setInterval);
            lua_setfield(L, -2, "setInterval");

            lua_pushcfunction(L, lua_clearInterval);
            lua_setfield(L, -2, "clearInterval");

            lua_pop(L, 1);  // Pop real _G
            return;
        }
        lua_pop(L, 2);  // Pop __index and metatable
    }

    // No proxy, just use _G directly
    lua_pop(L, 1);  // Pop whatever we got from LUA_RIDX_GLOBALS

    // Register as globals
    lua_pushcfunction(L, lua_setTimeout);
    lua_setglobal(L, "setTimeout");

    lua_pushcfunction(L, lua_clearTimeout);
    lua_setglobal(L, "clearTimeout");

    lua_pushcfunction(L, lua_setInterval);
    lua_setglobal(L, "setInterval");

    lua_pushcfunction(L, lua_clearInterval);
    lua_setglobal(L, "clearInterval");
}

} // namespace mosis
