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 Authn = {}

-- Cache for authn results
-- Max 1000 entries
-- Cache key is hash of auth0_token to avoid storing sensitive data
local authn_cache, cache_err = lrucache.new(1000)
if not authn_cache then
  error("Failed to create authn_cache: " .. (cache_err or "unknown error"))
end

-- Generate cache key from auth0_token
-- Use MD5 hash to avoid storing sensitive token in cache key
-- MD5 is faster than SHA256 and sufficient for cache key generation
local function generate_cache_key(auth0_token)
  local md5 = resty_md5:new()
  md5:update(auth0_token)
  local digest = md5:final()
  return str.to_hex(digest)
end

-- Internal function to query authn service via HTTP
-- Returns: authn_result (table with check_result, check_message, user_info) or nil, error_message
local function query_authn_http(authn_url, auth0_token, timeout)
  local request_body = {
    auth0_token = auth0_token,
  }

  local body_json, encode_err = cjson.encode(request_body)
  if not body_json then
    -- Include partial token for debugging, but avoid logging full token
    local token_prefix = string.sub(auth0_token, 1, 10)
    return nil, "Failed to encode authn request: " .. (encode_err or "unknown error") ..
                ", token_prefix: " .. token_prefix
  end

  local httpc = http_client.new({
    timeout = timeout or 10000,
    max_retries = 2,
    retry_delay = 50,
  })

  local res, err = httpc:request_uri(authn_url, {
    method = "POST",
    headers = {
      ["Content-Type"] = "application/json",
    },
    body = body_json,
  })

  if err or not res then
    return nil, "Failed to query authn service: " .. (err or "unknown error")
  end

  if res.status ~= 200 then
    return nil, "Authn service returned error status: " .. res.status
  end

  local response_body, decode_err = cjson.decode(res.body)
  if decode_err or type(response_body) ~= "table" then
    return nil, "Failed to decode authn response: " .. (decode_err or "not a JSON object")
  end

  -- The authn service returns { check_result, check_message, user_info }
  if response_body.check_result == nil then
    return nil, "Authn service response missing check_result field"
  end

  return response_body, nil
end

-- Authenticate with external authn service
-- Parameters:
--   authn_url: The full URL of the authn service (e.g., "http://account-svc:11000/inner/account-provider/api/v1/user/authn")
--   auth0_token: The Auth0 authentication token
--   timeout: Optional timeout in milliseconds (default: 10000)
--   cache_ttl: Optional cache TTL in seconds (default: 1800). Set to 0 to disable caching.
-- Returns:
--   authn_result: Table with { check_result (bool), user_id (int or nil) }
--   error: Error message if the check fails
function Authn.authenticate(authn_url, auth0_token, timeout, cache_ttl)
  if not authn_url or authn_url == "" then
    return nil, "Authn service URL is not configured"
  end

  if not auth0_token or auth0_token == "" then
    return nil, "Auth0 token is required"
  end

  -- Default cache TTL is 1800 seconds (30 minutes)
  cache_ttl = cache_ttl or 1800
  local cache_key = generate_cache_key(auth0_token)

  -- First check: return cached result if valid (fast path)
  -- Skip cache if TTL is 0
  if cache_ttl > 0 then
    local cached = authn_cache:get(cache_key)
    if cached ~= nil then
      return cached, nil
    end
  end

  -- Cache miss, acquire lock to query authn service
  local lock_key = "authn:" .. cache_key
  local lock, lock_err = resty_lock:new("o11y_proxy_locks", {
    timeout = 5,
    exptime = 10,
  })
  local locked = false

  if not lock then
    kong.log.warn("Failed to create lock, querying without lock: ", lock_err, ", trace_id: ",
        kong.ctx.shared.trace_id or "-")
  else
    local elapsed, err = lock:lock(lock_key)
    if not elapsed then
      kong.log.warn("Failed to acquire lock, querying without lock: ", err, ", trace_id: ",
          kong.ctx.shared.trace_id or "-")
    else
      locked = true
      -- Double-check: another request might have populated cache
      local cached = authn_cache:get(cache_key)
      if cached ~= nil then
        lock:unlock()
        return cached, nil
      end
    end
  end

  local authn_result, query_err = query_authn_http(authn_url, auth0_token, timeout)
  if authn_result == nil then
    if locked then lock:unlock() end
    return nil, query_err
  end

  -- Extract only check_result and user_id to reduce memory usage
  local result = {
    check_result = authn_result.check_result,
    user_id = authn_result.user_info and authn_result.user_info.id or nil,
  }

  -- Cache the simplified result
  -- Note: We cache both success (check_result=true) and failure (check_result=false) results
  if cache_ttl > 0 then
    authn_cache:set(cache_key, result, cache_ttl)
  end

  if locked then lock:unlock() end
  return result, nil
end

return Authn
