"""
LUA Scripts for Atomic Inventory Operations.

These scripts execute atomically in Redis, ensuring no race conditions
between checking availability and reserving inventory.

Key Schema:
    inv:{warehouse_id}:{sku} -> HASH {
        "available": int,           # ERP quantity minus all reservations
        "erp_quantity": int,        # Raw quantity from ERP
        "reserved_medusa": int,     # Reserved by Medusa orders
        "reserved_shopify": int     # Reserved by Shopify orders
    }
"""

# RESERVE_SCRIPT
# Atomically checks availability and reserves inventory.
# Returns JSON with success status and current state.
#
# KEYS[1] = inv:{warehouse_id}:{sku}
# ARGV[1] = quantity to reserve (int)
# ARGV[2] = source ("medusa" or "shopify")
RESERVE_SCRIPT = """
local key = KEYS[1]
local qty = tonumber(ARGV[1])
local source = ARGV[2]

-- Validate inputs
if qty <= 0 then
    return cjson.encode({
        success = false,
        reason = "invalid_quantity",
        message = "Quantity must be positive"
    })
end

-- Get current state
local available = tonumber(redis.call('HGET', key, 'available') or '0')
local reserved_key = 'reserved_' .. source
local current_reserved = tonumber(redis.call('HGET', key, reserved_key) or '0')

-- Check if enough available
if available < qty then
    return cjson.encode({
        success = false,
        reason = "insufficient_stock",
        available = available,
        requested = qty,
        reserved = current_reserved,
        source = source
    })
end

-- Atomic reserve: decrement available, increment reserved
redis.call('HINCRBY', key, 'available', -qty)
redis.call('HINCRBY', key, reserved_key, qty)

-- Get updated values
local new_available = tonumber(redis.call('HGET', key, 'available'))
local new_reserved = tonumber(redis.call('HGET', key, reserved_key))

return cjson.encode({
    success = true,
    available = new_available,
    reserved = new_reserved,
    source = source,
    quantity_reserved = qty
})
"""

# RELEASE_SCRIPT
# Atomically releases reserved inventory.
# For cancellation: returns items to available stock.
# For fulfillment: items are shipped out, available stays unchanged, erp_quantity decreases.
#
# KEYS[1] = inv:{warehouse_id}:{sku}
# ARGV[1] = quantity to release (int)
# ARGV[2] = source ("medusa" or "shopify")
# ARGV[3] = reason ("fulfilled", "cancelled", "expired")
RELEASE_SCRIPT = """
local key = KEYS[1]
local qty = tonumber(ARGV[1])
local source = ARGV[2]
local reason = ARGV[3] or 'cancelled'

-- Validate inputs
if qty <= 0 then
    return cjson.encode({
        success = false,
        reason = "invalid_quantity",
        message = "Quantity must be positive"
    })
end

-- Get current reserved for this source
local reserved_key = 'reserved_' .. source
local current_reserved = tonumber(redis.call('HGET', key, reserved_key) or '0')

-- Can't release more than reserved
local release_qty = math.min(qty, current_reserved)

if release_qty == 0 then
    -- Nothing to release
    local available = tonumber(redis.call('HGET', key, 'available') or '0')
    local erp_quantity = tonumber(redis.call('HGET', key, 'erp_quantity') or '0')
    return cjson.encode({
        success = true,
        released = 0,
        available = available,
        reserved = current_reserved,
        erp_quantity = erp_quantity,
        source = source,
        reason = reason,
        message = "Nothing to release"
    })
end

-- Always decrement reserved count
redis.call('HINCRBY', key, reserved_key, -release_qty)

-- Handle based on reason
if reason == 'fulfilled' then
    -- Fulfillment: item shipped out, decrement erp_quantity, available stays same
    -- (available = erp_quantity - total_reserved, both decrease so available unchanged)
    redis.call('HINCRBY', key, 'erp_quantity', -release_qty)
else
    -- Cancellation/Expiry: item returns to available stock
    redis.call('HINCRBY', key, 'available', release_qty)
end

-- Get updated values
local new_available = tonumber(redis.call('HGET', key, 'available'))
local new_reserved = tonumber(redis.call('HGET', key, reserved_key))
local new_erp_quantity = tonumber(redis.call('HGET', key, 'erp_quantity') or '0')

return cjson.encode({
    success = true,
    released = release_qty,
    available = new_available,
    reserved = new_reserved,
    erp_quantity = new_erp_quantity,
    source = source,
    reason = reason
})
"""

# SET_INVENTORY_SCRIPT
# Called by Storehouse when ERP sends inventory updates.
# Sets the ERP quantity and recalculates available based on current reservations.
#
# KEYS[1] = inv:{warehouse_id}:{sku}
# ARGV[1] = erp_quantity (int)
SET_INVENTORY_SCRIPT = """
local key = KEYS[1]
local erp_qty = tonumber(ARGV[1])

-- Validate input
if erp_qty < 0 then
    return cjson.encode({
        success = false,
        reason = "invalid_quantity",
        message = "ERP quantity cannot be negative"
    })
end

-- Get current reservations
local reserved_medusa = tonumber(redis.call('HGET', key, 'reserved_medusa') or '0')
local reserved_shopify = tonumber(redis.call('HGET', key, 'reserved_shopify') or '0')
local total_reserved = reserved_medusa + reserved_shopify

-- Calculate new available (cannot be negative)
local new_available = math.max(0, erp_qty - total_reserved)

-- Update hash fields
redis.call('HSET', key, 'erp_quantity', erp_qty)
redis.call('HSET', key, 'available', new_available)

return cjson.encode({
    success = true,
    erp_quantity = erp_qty,
    available = new_available,
    reserved_medusa = reserved_medusa,
    reserved_shopify = reserved_shopify,
    total_reserved = total_reserved
})
"""

# CHECK_AVAILABLE_SCRIPT
# Simple read of current available quantity.
# Can be used for pre-checkout availability checks.
#
# KEYS[1] = inv:{warehouse_id}:{sku}
CHECK_AVAILABLE_SCRIPT = """
local key = KEYS[1]
local available = tonumber(redis.call('HGET', key, 'available') or '0')
return available
"""

# GET_INVENTORY_STATE_SCRIPT
# Returns full inventory state for a SKU/warehouse.
# Useful for debugging and monitoring.
#
# KEYS[1] = inv:{warehouse_id}:{sku}
GET_INVENTORY_STATE_SCRIPT = """
local key = KEYS[1]

local available = tonumber(redis.call('HGET', key, 'available') or '0')
local erp_quantity = tonumber(redis.call('HGET', key, 'erp_quantity') or '0')
local reserved_medusa = tonumber(redis.call('HGET', key, 'reserved_medusa') or '0')
local reserved_shopify = tonumber(redis.call('HGET', key, 'reserved_shopify') or '0')

return cjson.encode({
    available = available,
    erp_quantity = erp_quantity,
    reserved_medusa = reserved_medusa,
    reserved_shopify = reserved_shopify,
    total_reserved = reserved_medusa + reserved_shopify
})
"""

# BULK_SET_INVENTORY_SCRIPT
# Sets inventory for multiple SKUs in a single atomic operation.
# Used during bulk ERP updates.
#
# KEYS = list of inv:{warehouse_id}:{sku} keys
# ARGV = list of erp quantities (same order as KEYS)
BULK_SET_INVENTORY_SCRIPT = """
local results = {}

for i, key in ipairs(KEYS) do
    local erp_qty = tonumber(ARGV[i])
    
    if erp_qty and erp_qty >= 0 then
        -- Get current reservations
        local reserved_medusa = tonumber(redis.call('HGET', key, 'reserved_medusa') or '0')
        local reserved_shopify = tonumber(redis.call('HGET', key, 'reserved_shopify') or '0')
        local total_reserved = reserved_medusa + reserved_shopify
        
        -- Calculate new available
        local new_available = math.max(0, erp_qty - total_reserved)
        
        -- Update hash
        redis.call('HSET', key, 'erp_quantity', erp_qty)
        redis.call('HSET', key, 'available', new_available)
        
        table.insert(results, {
            key = key,
            success = true,
            available = new_available
        })
    else
        table.insert(results, {
            key = key,
            success = false,
            reason = "invalid_quantity"
        })
    end
end

return cjson.encode(results)
"""
