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

namespace mosis {

NetworkManager::NetworkManager(const std::string& app_id, const NetworkLimits& limits)
    : m_app_id(app_id)
    , m_limits(limits)
    , m_mock_mode(true)
{
}

NetworkManager::~NetworkManager() {
}

void NetworkManager::SetAllowedDomains(const std::vector<std::string>& domains) {
    m_validator.SetAllowedDomains(domains);
}

void NetworkManager::ClearDomainRestrictions() {
    m_validator.ClearDomainRestrictions();
}

bool NetworkManager::ValidateRequest(const HttpRequest& request, std::string& error) {
    // Validate URL
    auto parsed = m_validator.Validate(request.url, error);
    if (!parsed) {
        return false;
    }

    // Validate method
    std::string method = request.method;
    std::transform(method.begin(), method.end(), method.begin(),
                   [](unsigned char c) { return std::toupper(c); });

    static const std::vector<std::string> allowed_methods = {
        "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"
    };

    bool method_valid = false;
    for (const auto& m : allowed_methods) {
        if (method == m) {
            method_valid = true;
            break;
        }
    }
    if (!method_valid) {
        error = "Invalid HTTP method: " + request.method;
        return false;
    }

    // Validate request body size
    if (request.body.size() > m_limits.max_request_body) {
        error = "Request body too large: " + std::to_string(request.body.size()) +
                " bytes (max " + std::to_string(m_limits.max_request_body) + ")";
        return false;
    }

    // Validate timeout
    if (request.timeout_ms > m_limits.max_timeout_ms) {
        error = "Timeout too large: " + std::to_string(request.timeout_ms) +
                "ms (max " + std::to_string(m_limits.max_timeout_ms) + "ms)";
        return false;
    }

    // Check concurrent request limit
    if (m_active_requests.load() >= m_limits.max_concurrent_requests) {
        error = "Too many concurrent requests (max " +
                std::to_string(m_limits.max_concurrent_requests) + ")";
        return false;
    }

    return true;
}

HttpResponse NetworkManager::Request(const HttpRequest& request, std::string& error) {
    HttpResponse response;

    // Validate the request
    if (!ValidateRequest(request, error)) {
        response.error = error;
        return response;
    }

    // In mock mode, we don't actually make network calls
    // This is for testing the validation logic
    if (m_mock_mode) {
        error = "Network requests disabled in mock mode";
        response.error = error;
        return response;
    }

    // Track active requests
    m_active_requests++;

    // In a real implementation, we would make the HTTP request here
    // For now, just return an error indicating no network implementation
    error = "Network requests not implemented on this platform";
    response.error = error;

    m_active_requests--;
    return response;
}

int NetworkManager::GetActiveRequestCount() const {
    return m_active_requests.load();
}

// Lua API implementation

// Get NetworkManager from upvalue
static NetworkManager* GetManager(lua_State* L) {
    return static_cast<NetworkManager*>(lua_touserdata(L, lua_upvalueindex(1)));
}

// network.request(options) -> response, error
static int L_network_request(lua_State* L) {
    NetworkManager* manager = GetManager(L);
    if (!manager) {
        lua_pushnil(L);
        lua_pushstring(L, "NetworkManager not available");
        return 2;
    }

    // Expect table argument
    luaL_checktype(L, 1, LUA_TTABLE);

    HttpRequest request;

    // Get URL (required)
    lua_getfield(L, 1, "url");
    if (!lua_isstring(L, -1)) {
        lua_pushnil(L);
        lua_pushstring(L, "url is required and must be a string");
        return 2;
    }
    request.url = lua_tostring(L, -1);
    lua_pop(L, 1);

    // Get method (optional, default GET)
    lua_getfield(L, 1, "method");
    if (lua_isstring(L, -1)) {
        request.method = lua_tostring(L, -1);
    }
    lua_pop(L, 1);

    // Get headers (optional)
    lua_getfield(L, 1, "headers");
    if (lua_istable(L, -1)) {
        lua_pushnil(L);
        while (lua_next(L, -2) != 0) {
            if (lua_isstring(L, -2) && lua_isstring(L, -1)) {
                request.headers[lua_tostring(L, -2)] = lua_tostring(L, -1);
            }
            lua_pop(L, 1);
        }
    }
    lua_pop(L, 1);

    // Get body (optional)
    lua_getfield(L, 1, "body");
    if (lua_isstring(L, -1)) {
        size_t len;
        const char* body = lua_tolstring(L, -1, &len);
        request.body = std::string(body, len);
    }
    lua_pop(L, 1);

    // Get timeout (optional)
    lua_getfield(L, 1, "timeout");
    if (lua_isnumber(L, -1)) {
        request.timeout_ms = static_cast<int>(lua_tointeger(L, -1));
    }
    lua_pop(L, 1);

    // Make request
    std::string error;
    HttpResponse response = manager->Request(request, error);

    if (!error.empty()) {
        lua_pushnil(L);
        lua_pushstring(L, error.c_str());
        return 2;
    }

    // Return response as table
    lua_newtable(L);

    lua_pushinteger(L, response.status_code);
    lua_setfield(L, -2, "status");

    lua_pushstring(L, response.body.c_str());
    lua_setfield(L, -2, "body");

    // Headers table
    lua_newtable(L);
    for (const auto& [key, value] : response.headers) {
        lua_pushstring(L, value.c_str());
        lua_setfield(L, -2, key.c_str());
    }
    lua_setfield(L, -2, "headers");

    if (!response.error.empty()) {
        lua_pushstring(L, response.error.c_str());
        lua_setfield(L, -2, "error");
    }

    return 1;  // Return response table
}

// 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
    lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);

    // Check if it's a proxy with __index pointing to real _G
    if (lua_getmetatable(L, -1)) {
        lua_getfield(L, -1, "__index");
        if (lua_istable(L, -1)) {
            // This is the real _G, set our value there
            lua_pushvalue(L, -4);  // Push the value
            lua_setfield(L, -2, name);
            lua_pop(L, 4);  // Pop __index, metatable, proxy, (value already consumed)
            return;
        }
        lua_pop(L, 2);  // Pop __index and metatable
    }

    // No proxy, set directly
    lua_pushvalue(L, -2);  // Push the value
    lua_setfield(L, -2, name);
    lua_pop(L, 2);  // Pop globals table and original value
}

void RegisterNetworkAPI(lua_State* L, NetworkManager* manager) {
    // Create network table
    lua_newtable(L);

    // Add request function with manager as upvalue
    lua_pushlightuserdata(L, manager);
    lua_pushcclosure(L, L_network_request, 1);
    lua_setfield(L, -2, "request");

    // Set as global
    SetGlobalInRealG(L, "network");
}

} // namespace mosis
