local http_client = require "kong.plugins.o11y-proxy.http_client"
local cjson = require "cjson.safe"
local lrucache = require "resty.lrucache"
local resty_lock = require "resty.lock"
local resty_md5 = require "resty.md5"
local str = require "resty.string"

local Authz = {}

-- Cache for authz results
-- Max 10000 entries (larger than authn since we cache per user_id + resource_uri + req_uri)
-- Cache key is hash of user_id + resource_uri + req_uri
local authz_cache, cache_err = lrucache.new(10000)
if not authz_cache then
  error("Failed to create authz_cache: " .. (cache_err or "unknown error"))
end

-- Generate cache key from user_id, resource_uri, and req_uri
-- Use MD5 hash for efficient cache key generation
local function generate_cache_key(user_id, resource_uri, req_uri)
  local md5 = resty_md5:new()
  md5:update(tostring(user_id))
  md5:update(":")
  md5:update(resource_uri)
  md5:update(":")
  md5:update(req_uri)
  local digest = md5:final()
  return str.to_hex(digest)
end

-- Internal function to query authz service via HTTP
-- Returns: authz_result (table with has_permission, message) or nil, error_message
local function query_authz_http(authz_url, user_id, resource_uri, req_uri, timeout)
  local request_body = {
    user_id = user_id,
    resource_uri = resource_uri,
    req_uri = req_uri,
  }
  
  local body_json, encode_err = cjson.encode(request_body)
  if not body_json then
    return nil, "Failed to encode authz request: " .. (encode_err or "unknown error") .. 
                ", user_id: " .. tostring(user_id) .. 
                ", resource_uri: " .. resource_uri .. 
                ", req_uri: " .. req_uri
  end
  
  local httpc = http_client.new({
    timeout = timeout or 10000,
    max_retries = 2,
    retry_delay = 50,
  })
  
  local res, err = httpc:request_uri(authz_url, {
    method = "POST",
    headers = {
      ["Content-Type"] = "application/json",
    },
    body = body_json,
  })
  
  if err or not res then
    return nil, "Failed to query authz service: " .. (err or "unknown error")
  end
  
  if res.status ~= 200 then
    return nil, "Authz service returned error status: " .. res.status
  end
  
  local response_body, decode_err = cjson.decode(res.body)
  if decode_err or not response_body then
    return nil, "Failed to decode authz response: " .. (decode_err or "unknown error")
  end
  
  -- The authz service returns { has_permission, message }
  if response_body.has_permission == nil then
    return nil, "Authz service response missing has_permission field"
  end
  
  return response_body, nil
end

-- Check authorization with external authz service
-- Parameters:
--   authz_url: The full URL of the authz service (e.g., "http://account-svc:11000/inner/account-provider/api/v1/user/authz")
--   user_id: The authenticated user's ID
--   resource_uri: The resource URI being accessed (registered in the account service)
--   req_uri: The request URI that the user is trying to access
--   timeout: Optional timeout in milliseconds (default: 10000)
--   cache_ttl: Optional cache TTL in seconds (default: 60). Set to 0 to disable caching.
-- Returns:
--   authz_result: Table with { has_permission (bool) }
--   error: Error message if the check fails
function Authz.check_permission(authz_url, user_id, resource_uri, req_uri, timeout, cache_ttl)
  if not authz_url or authz_url == "" then
    return nil, "Authz service URL is not configured"
  end
  
  if not user_id then
    return nil, "User ID is required"
  end
  
  if not resource_uri or resource_uri == "" then
    return nil, "Resource URI is required"
  end
  
  if not req_uri or req_uri == "" then
    return nil, "Request URI is required"
  end
  
  -- Default cache TTL is 60 seconds
  cache_ttl = cache_ttl or 60
  local cache_key = generate_cache_key(user_id, resource_uri, req_uri)
  
  -- First check: return cached result if valid (fast path)
  -- Skip cache if TTL is 0
  if cache_ttl > 0 then
    local cached = authz_cache:get(cache_key)
    if cached ~= nil then
      return cached, nil
    end
  end
  
  -- Cache miss, acquire lock to query authz service
  local lock_key = "authz:" .. cache_key
  local lock, lock_err = resty_lock:new("o11y_proxy_locks", {
    timeout = 5,
    exptime = 10,
  })
  
  if not lock then
    kong.log.warn("Failed to create lock, querying without lock: ", lock_err)
  else
    local elapsed, err = lock:lock(lock_key)
    if not elapsed then
      kong.log.warn("Failed to acquire lock, querying without lock: ", err)
    else
      -- Double-check: another request might have populated cache
      local cached = authz_cache:get(cache_key)
      if cached ~= nil then
        lock:unlock()
        return cached, nil
      end
    end
  end
  
  local authz_result, query_err = query_authz_http(authz_url, user_id, resource_uri, req_uri, timeout)
  if authz_result == nil then
    if lock then lock:unlock() end
    return nil, query_err
  end
  
  -- Extract only has_permission to reduce memory usage
  local result = {
    has_permission = authz_result.has_permission,
  }
  
  -- Cache the simplified result
  -- Note: We cache both success (has_permission=true) and failure (has_permission=false) results
  if cache_ttl > 0 then
    authz_cache:set(cache_key, result, cache_ttl)
  end
  
  if lock then lock:unlock() end
  return result, nil
end

return Authz
