#!lua name=modsrv

-- ========================================
-- VoltageEMS Instance Service Functions
-- Version: 1.0.0
-- ========================================

-- Redis 8.0+ has cjson as a global
-- No need to declare, just use cjson directly

-- Safe JSON decode helper (unified error handling)
local function safe_decode(json_str, what)
	if not json_str then
		return nil, redis.error_reply("Missing " .. (what or "JSON"))
	end
	local ok, result = pcall(cjson.decode, json_str)
	if not ok then
		return nil, redis.error_reply("Invalid " .. (what or "JSON") .. ": " .. tostring(result))
	end
	return result
end

-- ============ Point Routing Functions ============

-- Load channel-instance point routing batch
-- @function modsrv_load_routing
-- @input keys: unused
-- @input args[1]: routing_json - JSON array of routing definitions
-- @output string - Success message with route count
-- @throws redis.error_reply - Invalid JSON or missing arguments
-- @redis-write route:c2m - Forward routing (comsrv to modsrv)
-- @redis-write route:m2c - Reverse routing for actions (modsrv to comsrv)
-- @side-effects Populates routing tables for data flow
local function modsrv_load_routing(keys, args)
	if #args < 1 then
		return redis.error_reply("Usage: routing_json")
	end

	local routing_json = args[1]
	local routing, err = safe_decode(routing_json, "routing")
	if err then
		return err
	end

	local count = 0
	for _, route in ipairs(routing) do
		-- Store forward routing: comsrv -> modsrv
		redis.call("HSET", "route:c2m", route.comsrv_key, route.modsrv_key)
		count = count + 1

		-- Store reverse routing for actions: modsrv -> comsrv
		if route.is_action then
			redis.call("HSET", "route:m2c", route.modsrv_key, route.comsrv_key)
		end
	end

	return string.format("Loaded %d routes", count)
end

-- NOTE: Removed modsrv_route_data - redundant with comsrv_batch_update routing logic

-- Internal helper: Route action to comsrv (shared by multiple functions)
-- This function handles the complete routing logic: mapping lookup, value processing, and dual write (TODO + Hash)
-- @function route_action_to_comsrv
-- @input instance_id: string - Instance identifier
-- @input point_id: string/number - Action point ID
-- @input value: number - Action value to send
-- @output boolean, string - Success flag and status/error message
-- @redis-read route:m2c - Reverse routing lookup
-- @redis-write comsrv:{id}:{type}:TODO - Command queue (trigger)
-- @redis-write comsrv:{id}:{type} - State hash (recording)
-- @critical-order TODO first (trigger), then Hash (state)
local function route_action_to_comsrv(instance_id, point_id, value)
	-- Build modsrv key for mapping lookup
	local modsrv_key = string.format("modsrv:%s:A:%s", instance_id, point_id)

	-- Look up reverse routing
	local comsrv_key = redis.call("HGET", "route:m2c", modsrv_key)
	if not comsrv_key then
		return false, "NO_ROUTING"
	end

	-- Parse comsrv key to extract channel info
	local channel_id, point_type, comsrv_point_id = string.match(comsrv_key, "comsrv:(%d+):([CA]):(%d+)")
	if not channel_id then
		return false, "INVALID_KEY_FORMAT"
	end

	-- Ensure value is numeric
	local value_to_store = tonumber(value)
	if not value_to_store then
		return false, "INVALID_VALUE"
	end

	-- For Control type, ensure it's 0 or 1
	if point_type == "C" then
		value_to_store = (value_to_store ~= 0) and 1 or 0
	end

	-- CRITICAL: First TODO (trigger execution), then Hash (state storage)
	-- This ensures control action priority over state recording

	-- 1. Add to ComSrv command queue for execution (trigger)
	local queue_key = string.format("comsrv:%s:%s:TODO", channel_id, point_type)
	local time_now = redis.call("TIME")
	local timestamp_ms = time_now[1] * 1000 + math.floor(time_now[2] / 1000)
	local control_data = cjson.encode({
		command_id = string.format("modsrv_%d_%d", timestamp_ms, math.random(100000)),
		channel_id = tonumber(channel_id),
		command_type = point_type, -- "C" or "A"
		point_id = tonumber(comsrv_point_id),
		value = value_to_store,
		timestamp = timestamp_ms, -- Use milliseconds consistently
		metadata = {},
		source = "modsrv",
	})

	-- Check queue length before adding to detect potential issues
	local queue_len = redis.call("LLEN", queue_key)
	if queue_len >= 500 then
		-- Log warning to stats (comsrv likely has issues if this happens)
		redis.call("HINCRBY", "modsrv:stats:warnings", "queue_overflow_" .. channel_id .. "_" .. point_type, 1)
		redis.call("HSET", "modsrv:stats:last_overflow", channel_id .. "_" .. point_type, timestamp_ms)
		-- Publish warning notification for real-time alerting
		redis.call(
			"PUBLISH",
			"warnings:queue_overflow",
			cjson.encode({
				service = "modsrv",
				channel_id = tonumber(channel_id),
				point_type = point_type,
				queue_length = queue_len,
				timestamp = timestamp_ms,
				severity = "critical",
			})
		)
		-- Continue execution but trim oldest items to maintain queue size
	elseif queue_len >= 450 then
		-- Early warning when approaching limit
		redis.call("HINCRBY", "modsrv:stats:warnings", "queue_high_" .. channel_id .. "_" .. point_type, 1)
		-- Publish early warning notification
		redis.call(
			"PUBLISH",
			"warnings:queue_high",
			cjson.encode({
				service = "modsrv",
				channel_id = tonumber(channel_id),
				point_type = point_type,
				queue_length = queue_len,
				timestamp = timestamp_ms,
				severity = "warning",
			})
		)
	end

	-- FIFO: RPUSH producer, BLPOP consumer
	redis.call("RPUSH", queue_key, control_data)
	-- Control queue length to avoid unbounded growth
	redis.call("LTRIM", queue_key, -500, -1) -- Keep only the newest 500 items (tail)

	-- 2. Update comsrv Hash (state storage)
	local comsrv_hash_key = string.format("comsrv:%s:%s", channel_id, point_type)
	redis.call("HSET", comsrv_hash_key, comsrv_point_id, tostring(value_to_store))

	return true, channel_id
end

-- Unified A-point write interface (write triggers routing)
-- All A-point writes should go through this function to ensure automatic routing
-- @function modsrv_set_action_point
-- @input keys: unused
-- @input args[1]: instance_id - Target instance identifier
-- @input args[2]: point_id - Action point ID
-- @input args[3]: value - Action value to set
-- @output string - JSON result with routing status
-- @throws redis.error_reply - Missing arguments
-- @redis-write modsrv:{instance}:A:{point} - Instance action state
-- @side-effects Triggers automatic routing to comsrv TODO queue
-- @critical This is the ONLY interface for setting action points
local function modsrv_set_action_point(keys, args)
	if #args < 3 then
		return redis.error_reply("Usage: instance_id point_id value")
	end

	local instance_id = args[1]
	local point_id = args[2]
	local value = args[3]

	-- 1. First trigger routing (includes TODO write)
	local routed, result = route_action_to_comsrv(instance_id, point_id, value)

	-- 2. Then write to modsrv Hash (state storage)
	local hash_key = string.format("modsrv:%s:A", instance_id)
	redis.call("HSET", hash_key, point_id, value)

	return cjson.encode({
		status = "success",
		instance = instance_id,
		point = point_id,
		value = value,
		routed = routed,
		route_result = result,
	})
end

-- Get routing for a comsrv point
local function modsrv_get_routing(keys, args)
	if #args < 1 then
		return redis.error_reply("Usage: comsrv_key")
	end

	local comsrv_key = args[1]
	local modsrv_key = redis.call("HGET", "route:c2m", comsrv_key)

	return modsrv_key or ""
end

-- Clear all routing
local function modsrv_clear_routing(keys, args)
	redis.call("DEL", "route:c2m", "route:m2c")
	return "OK"
end

-- ============ Instance Service Functions ============

-- Upsert instance (create or update)
local function modsrv_upsert_instance(keys, args)
	if #args < 2 then
		return redis.error_reply("Usage: instance_id instance_json")
	end

	local instance_id = args[1]
	local instance_json = args[2]

	-- Parse instance data
	local instance, err = safe_decode(instance_json, "instance")
	if err then
		return err
	end

	-- Cache timestamp to avoid multiple TIME calls (use milliseconds)
	local time_now = redis.call("TIME")
	local now = time_now[1] * 1000 + math.floor(time_now[2] / 1000)

	-- Store instance info in Hash (for O(1) field updates)
	local info_key = string.format("instance:%s:info", instance_id)
	redis.call(
		"HSET",
		info_key,
		"id",
		instance_id,
		"name",
		instance.name or "",
		"product_id",
		instance.product_id or "",
		"properties",
		cjson.encode(instance.properties or {}),
		"created_at",
		instance.created_at or now,
		"updated_at",
		now
	)

	-- Store attributes in separate Hash
	if instance.attributes then
		local attr_key = string.format("instance:%s:attributes", instance_id)
		for _, attr in ipairs(instance.attributes) do
			redis.call("HSET", attr_key, attr.name, cjson.encode(attr))
		end
	end

	-- Store parameters in separate Hash (O(1) updates)
	if instance.parameters then
		local param_key = string.format("instance:%s:parameters", instance_id)
		for k, v in pairs(instance.parameters) do
			redis.call("HSET", param_key, k, tostring(v))
		end
	end

	-- Note: Removed instance:mappings storage - using route:c2m/m2c as single source of truth

	-- Initialize runtime data structures
	-- Status: offline, online, running, stopped, error
	local status_key = string.format("modsrv:%s:status", instance_id)
	redis.call("HSET", status_key, "state", "offline", "last_update", now)

	-- Initialize empty control queue (created lazily when actions are executed)

	-- Initialize config cache from instance properties
	local config_key = string.format("modsrv:%s:config", instance_id)
	if instance.properties then
		for k, v in pairs(instance.properties) do
			redis.call("HSET", config_key, k, tostring(v))
		end
	end

	-- Add to instance index
	redis.call("SADD", "instance:index", instance_id)

	return redis.status_reply("OK")
end

-- Get instance by ID
local function modsrv_get_instance(keys, args)
	if #args < 1 then
		return redis.error_reply("Usage: instance_id")
	end

	local instance_id = args[1]

	-- Check if instance exists
	if redis.call("SISMEMBER", "instance:index", instance_id) == 0 then
		return cjson.encode({ error = "Instance not found" })
	end

	-- Get instance info
	local info_key = string.format("instance:%s:info", instance_id)
	local info = redis.call("HGETALL", info_key)

	local instance = {}
	for i = 1, #info, 2 do
		instance[info[i]] = info[i + 1]
	end

	-- Get attributes
	local attr_key = string.format("instance:%s:attributes", instance_id)
	local attrs_data = redis.call("HGETALL", attr_key)
	local attributes = {}
	for i = 1, #attrs_data, 2 do
		local ok, attr = pcall(cjson.decode, attrs_data[i + 1])
		if ok then
			table.insert(attributes, attr)
		end
	end
	if #attributes > 0 then
		instance.attributes = attributes
	end

	-- Get parameters
	local param_key = string.format("instance:%s:parameters", instance_id)
	local params = redis.call("HGETALL", param_key)
	if #params > 0 then
		local parameters = {}
		for i = 1, #params, 2 do
			parameters[params[i]] = params[i + 1]
		end
		instance.parameters = parameters
	end

	return cjson.encode(instance)
end

-- List all instances
local function modsrv_list_instances(keys, args)
	local instance_ids = redis.call("SMEMBERS", "instance:index")
	local instances = {}

	for _, instance_id in ipairs(instance_ids) do
		local result = modsrv_get_instance({}, { instance_id })
		local ok, instance = pcall(cjson.decode, result)
		if ok and not instance.error then
			table.insert(instances, instance)
		end
	end

	return cjson.encode(instances)
end

-- Delete instance
local function modsrv_delete_instance(keys, args)
	if #args < 1 then
		return redis.error_reply("Usage: instance_id")
	end

	local instance_id = args[1]

	-- Delete all static instance keys
	redis.call("DEL", string.format("instance:%s:info", instance_id))
	redis.call("DEL", string.format("instance:%s:attributes", instance_id))
	redis.call("DEL", string.format("instance:%s:parameters", instance_id))

	-- Delete runtime keys
	redis.call("DEL", string.format("modsrv:%s:status", instance_id))
	redis.call("DEL", string.format("modsrv:%s:config", instance_id))
	redis.call("DEL", string.format("modsrv:%s:M", instance_id)) -- Measurement Hash
	redis.call("DEL", string.format("modsrv:%s:A", instance_id)) -- Action Hash

	-- Clean up any other modsrv:{id}:* keys that might exist
	-- Using SCAN to find and delete all related keys
	local cursor = "0"
	local pattern = string.format("modsrv:%s:*", instance_id)
	repeat
		local res = redis.call("SCAN", cursor, "MATCH", pattern, "COUNT", 100)
		cursor = res[1]
		local keys_to_delete = res[2]
		if #keys_to_delete > 0 then
			redis.call("DEL", unpack(keys_to_delete))
		end
	until cursor == "0"

	-- Clear routing for this instance (both c2m and m2c)
	local cursor2 = "0"
	local prefix = "modsrv:" .. instance_id .. ":"
	repeat
		local res = redis.call("HSCAN", "route:m2c", cursor2, "COUNT", 1000)
		cursor2 = res[1]
		local kv = res[2]
		for i = 1, #kv, 2 do
			local modsrv_key = kv[i]
			if string.sub(modsrv_key, 1, #prefix) == prefix then
				local comsrv_key = kv[i + 1]
				redis.call("HDEL", "route:m2c", modsrv_key)
				if comsrv_key then
					redis.call("HDEL", "route:c2m", comsrv_key)
				end
			end
		end
	until cursor2 == "0"

	-- Remove from index
	redis.call("SREM", "instance:index", instance_id)

	return redis.status_reply("OK")
end

-- Patch instance parameters (O(1) updates)
local function modsrv_patch_parameters(keys, args)
	if #args < 2 then
		return redis.error_reply("Usage: instance_id parameters_json")
	end

	local instance_id = args[1]
	local params_json = args[2]

	-- Parse parameters
	local ok, params = pcall(cjson.decode, params_json)
	if not ok then
		return redis.error_reply("Invalid parameters JSON")
	end

	-- Update parameters (O(1) per parameter)
	local param_key = string.format("instance:%s:parameters", instance_id)
	local updated = 0
	for k, v in pairs(params) do
		if v == cjson.null then
			redis.call("HDEL", param_key, k)
		else
			redis.call("HSET", param_key, k, tostring(v))
			updated = updated + 1
		end
	end

	-- Update instance timestamp (use milliseconds)
	local info_key = string.format("instance:%s:info", instance_id)
	local time_now = redis.call("TIME")
	local now_ms = time_now[1] * 1000 + math.floor(time_now[2] / 1000)
	redis.call("HSET", info_key, "updated_at", now_ms)

	return cjson.encode({ updated = updated })
end

-- Get instance parameters
local function modsrv_get_parameters(keys, args)
	if #args < 1 then
		return redis.error_reply("Usage: instance_id")
	end

	local instance_id = args[1]
	local param_key = string.format("instance:%s:parameters", instance_id)
	local params = redis.call("HGETALL", param_key)

	local result = {}
	for i = 1, #params, 2 do
		result[params[i]] = params[i + 1]
	end

	return cjson.encode(result)
end

-- Delete parameter
local function modsrv_delete_parameter(keys, args)
	if #args < 2 then
		return redis.error_reply("Usage: instance_id param_name")
	end

	local instance_id = args[1]
	local param_name = args[2]

	local param_key = string.format("instance:%s:parameters", instance_id)
	local deleted = redis.call("HDEL", param_key, param_name)

	return cjson.encode({ deleted = deleted })
end

-- Sync measurement data
local function modsrv_sync_measurement(keys, args)
	if #args < 2 then
		return redis.error_reply("Usage: instance_name measurement_json")
	end

	local instance_name = args[1]
	local measurement_json = args[2]

	-- Parse measurement data
	local ok, measurement = pcall(cjson.decode, measurement_json)
	if not ok then
		return redis.error_reply("Invalid measurement JSON")
	end

	-- Store in Hash format using M type (consistent with routing)
	local measurement_key = string.format("modsrv:%s:M", instance_name)

	for field, value in pairs(measurement) do
		redis.call("HSET", measurement_key, field, tostring(value))
	end

	-- Update timestamp (use milliseconds)
	local time_now = redis.call("TIME")
	local now_ms = time_now[1] * 1000 + math.floor(time_now[2] / 1000)
	redis.call("HSET", measurement_key, "_updated_at", now_ms)

	return redis.status_reply("OK")
end

-- Get instance data with measurements
local function modsrv_get_instance_data(keys, args)
	if #args < 1 then
		return redis.error_reply("Usage: instance_name [type]")
	end

	local instance_name = args[1]
	local dtype = args[2] -- optional: "measurement" | "action"

	if dtype == nil or dtype == "measurement" then
		-- Read from Hash storage (unified format)
		local hash_key = string.format("modsrv:%s:M", instance_name)
		local data = redis.call("HGETALL", hash_key)
		local result = {}

		-- Convert flat array to key-value pairs
		for i = 1, #data, 2 do
			local field = data[i]
			local value = data[i + 1]
			-- Skip metadata fields
			if not string.match(field, "^_") then
				result[field] = value
			end
		end

		return cjson.encode(result)
	elseif dtype == "action" then
		-- Read from Hash storage (unified format)
		local hash_key = string.format("modsrv:%s:A", instance_name)
		local data = redis.call("HGETALL", hash_key)
		local result = {}

		-- Convert flat array to key-value pairs
		for i = 1, #data, 2 do
			local field = data[i]
			local value = data[i + 1]
			-- Skip metadata fields
			if not string.match(field, "^_") then
				result[field] = value
			end
		end

		return cjson.encode(result)
	else
		return redis.error_reply("Unknown data type; use 'measurement' or 'action'")
	end
end

-- Sync all measurements batch
local function modsrv_sync_all_measurements(keys, args)
	if #args < 1 then
		return redis.error_reply("Usage: measurements_json")
	end

	local measurements_json = args[1]

	-- Parse batch data
	local ok, batch = pcall(cjson.decode, measurements_json)
	if not ok then
		return redis.error_reply("Invalid measurements JSON")
	end

	local synced = 0
	for instance_name, measurement in pairs(batch) do
		local result = modsrv_sync_measurement({}, {
			instance_name,
			cjson.encode(measurement),
		})
		if result == redis.status_reply("OK") then
			synced = synced + 1
		end
	end

	return cjson.encode({ synced = synced })
end

-- NOTE: Removed deprecated template compatibility functions and unused association management
-- These were adding unnecessary complexity without being used in production

-- Export functions
-- Routing functions
redis.register_function("modsrv_load_routing", modsrv_load_routing)
-- Unified A-point write interface (write triggers routing)
redis.register_function("modsrv_set_action_point", modsrv_set_action_point)
redis.register_function("modsrv_get_routing", modsrv_get_routing)
redis.register_function("modsrv_clear_routing", modsrv_clear_routing)
-- Routing cleanup helpers (per instance/channel)
local function modsrv_clear_routing_for_instance(keys, args)
	if #args < 1 then
		return redis.error_reply("Usage: instance_id")
	end
	local instance_id = args[1]
	local removed = 0
	local cursor = "0"
	local prefix = "modsrv:" .. instance_id .. ":"
	repeat
		local res = redis.call("HSCAN", "map:m2c", cursor, "COUNT", 1000)
		cursor = res[1]
		local kv = res[2]
		for i = 1, #kv, 2 do
			local modsrv_key = kv[i]
			if string.sub(modsrv_key, 1, #prefix) == prefix then
				local comsrv_key = kv[i + 1]
				redis.call("HDEL", "route:m2c", modsrv_key)
				if comsrv_key then
					redis.call("HDEL", "route:c2m", comsrv_key)
				end
				removed = removed + 1
			end
		end
	until cursor == "0"
	return cjson.encode({ removed = removed })
end

local function modsrv_clear_routing_for_channel(keys, args)
	if #args < 1 then
		return redis.error_reply("Usage: channel_id")
	end
	local channel_id = args[1]
	local removed = 0
	local cursor = "0"
	local pattern = "comsrv:" .. channel_id .. ":*"
	repeat
		local res = redis.call("HSCAN", "route:c2m", cursor, "MATCH", pattern, "COUNT", 1000)
		cursor = res[1]
		local kv = res[2]
		for i = 1, #kv, 2 do
			local comsrv_key = kv[i]
			local modsrv_key = kv[i + 1]
			redis.call("HDEL", "route:c2m", comsrv_key)
			if modsrv_key then
				redis.call("HDEL", "route:m2c", modsrv_key)
			end
			removed = removed + 1
		end
	until cursor == "0"
	return cjson.encode({ removed = removed })
end

redis.register_function("modsrv_clear_routing_for_instance", modsrv_clear_routing_for_instance)
redis.register_function("modsrv_clear_routing_for_channel", modsrv_clear_routing_for_channel)

-- Instance functions
redis.register_function("modsrv_upsert_instance", modsrv_upsert_instance)
redis.register_function("modsrv_delete_instance", modsrv_delete_instance)
redis.register_function("modsrv_sync_measurement", modsrv_sync_measurement)
redis.register_function("modsrv_get_instance_data", modsrv_get_instance_data)

-- Initialize runtime data for an instance
local function modsrv_init_runtime(keys, args)
	if #args < 1 then
		return redis.error_reply("Usage: instance_id")
	end

	local instance_id = args[1]

	-- Check if instance exists
	if redis.call("SISMEMBER", "instance:index", instance_id) == 0 then
		return redis.error_reply("Instance not found")
	end

	-- Initialize status
	local status_key = string.format("modsrv:%s:status", instance_id)
	local time_now = redis.call("TIME")
	local now_ms = time_now[1] * 1000 + math.floor(time_now[2] / 1000)
	redis.call("HSET", status_key, "state", "offline")
	redis.call("HSET", status_key, "last_update", now_ms)
	redis.call("HSET", status_key, "health", "unknown")
	redis.call("HSET", status_key, "errors", "0")

	-- Copy config from instance properties
	local info_key = string.format("instance:%s:info", instance_id)
	local properties_json = redis.call("HGET", info_key, "properties")
	if properties_json then
		local ok, properties = pcall(cjson.decode, properties_json)
		if ok and properties then
			local config_key = string.format("modsrv:%s:config", instance_id)
			for k, v in pairs(properties) do
				redis.call("HSET", config_key, k, tostring(v))
			end
		end
	end

	return redis.status_reply("OK")
end

redis.register_function("modsrv_init_runtime", modsrv_init_runtime)

-- Register additional instance and parameter management functions
redis.register_function("modsrv_list_instances", modsrv_list_instances)
redis.register_function("modsrv_patch_parameters", modsrv_patch_parameters)
redis.register_function("modsrv_get_parameters", modsrv_get_parameters)
redis.register_function("modsrv_delete_parameter", modsrv_delete_parameter)
redis.register_function("modsrv_sync_all_measurements", modsrv_sync_all_measurements)

-- ============ Product Management Functions ============

-- Store product definition in Redis
local function modsrv_upsert_product(keys, args)
	if #args < 2 then
		return redis.error_reply("Usage: product_id product_json")
	end

	local product_id = args[1]
	local product_json = args[2]

	-- Parse JSON to validate
	local ok, product = pcall(cjson.decode, product_json)
	if not ok then
		return redis.error_reply("Invalid product JSON")
	end

	-- Store product definition
	local product_key = string.format("modsrv:product:%s", product_id)
	local time_now = redis.call("TIME")
	local now_ms = time_now[1] * 1000 + math.floor(time_now[2] / 1000)
	redis.call("HSET", product_key, "definition", product_json)
	redis.call("HSET", product_key, "updated_at", now_ms)

	-- Store product ID in index
	redis.call("SADD", "modsrv:products", product_id)

	-- Store parent-child relationship if parent exists
	if product.parent_id and product.parent_id ~= cjson.null then
		local parent_key = string.format("modsrv:product:%s:children", product.parent_id)
		redis.call("SADD", parent_key, product_id)
	end

	-- Store measurements index
	if product.measurements then
		for _, m in ipairs(product.measurements) do
			local mkey = string.format("modsrv:product:%s:measurements", product_id)
			redis.call("HSET", mkey, tostring(m.id), cjson.encode(m))
		end
	end

	-- Store actions index
	if product.actions then
		for _, a in ipairs(product.actions) do
			local akey = string.format("modsrv:product:%s:actions", product_id)
			redis.call("HSET", akey, tostring(a.id), cjson.encode(a))
		end
	end

	-- Store properties index
	if product.properties then
		for _, p in ipairs(product.properties) do
			local pkey = string.format("modsrv:product:%s:properties", product_id)
			redis.call("HSET", pkey, p.name, cjson.encode(p))
		end
	end

	return redis.status_reply("OK")
end

redis.register_function("modsrv_upsert_product", modsrv_upsert_product)
