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

-- Cache for routing data
-- Max 10000 entries, 5 minutes TTL (routing config is relatively stable)
local route_cache, cache_err = lrucache.new(10000)
if not route_cache then
  error("Failed to create route_cache: " .. (cache_err or "unknown error"))
end

-- Internal function to query router service via HTTP
-- Returns: route_data or nil, error_message
local function query_router_http(router_url, region_id, org_id, biz_type, project_id, application_id, timeout)
  local httpc = http_client.new({
    timeout = timeout or 5000,  -- Default 5 seconds for internal service
    max_retries = 2,
    retry_delay = 50,
  })

  local headers = {
    ["X-Region-Id"] = region_id,
    ["X-Org-Id"] = org_id,
    ["X-Biz-Type"] = biz_type,
  }

  -- Forward 4-segment routing headers so GS can do project-level
  -- and child→parent cluster resolution.
  if project_id and project_id ~= "" then
    headers["X-Project-Id"] = project_id
  end
  if application_id and application_id ~= "" then
    headers["X-Application-Id"] = application_id
  end

  local res, err = httpc:request_uri(router_url, {
    method = "GET",
    headers = headers,
  })

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

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

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

  if not body.data then
    return nil, "Router service response missing data"
  end

  if not body.data.dataplaneAddress then
    return nil, "Router service response missing dataplaneAddress"
  end

  -- Return the complete route data including credentials information
  return {
    dns_address = body.data.dataplaneAddress,
    access_principal = body.data.accessPrincipal,
    access_external_id = body.data.accessExternalID,
    logical_region_name = body.data.logicalRegionName,
  }, nil
end

-- Query router service to get routing information
-- Parameters:
--   router_url: The full URL of the router service (e.g., "http://globalserver-svc.observability:8080/internal/v1/routes")
--   region_id: The region ID from X-Region-Id header (e.g., "aws-us-west-2")
--   org_id: The organization/tenant ID from X-Org-Id header
--   biz_type: The business type from X-Biz-Type header (e.g., "byoc")
--   timeout: Optional timeout in milliseconds (default: 5000)
--   cache_ttl: Optional cache TTL in seconds (default: 300). Set to 0 to disable caching.
-- Returns:
--   route_data: Table containing:
--     - dns_address: The target URL to forward the request to (e.g., "https://l5vgwkcldg.execute-api.us-west-2.amazonaws.com/prod")
--     - access_principal: The AWS IAM role ARN to assume (e.g., "arn:aws:iam::123456789012:role/MyRole")
--     - access_external_id: The external ID for AWS role assumption (optional)
--     - logical_region_name: The logical region name (e.g., "aws-us-west-2")
--   error: Error message if the query fails
function Router.query_target(router_url, region_id, org_id, biz_type, project_id, application_id, timeout, cache_ttl)
  if not router_url or router_url == "" then
    return nil, "Router service URL is not configured"
  end

  if not region_id or region_id == "" then
    return nil, "X-Region-Id header is required"
  end

  if not org_id or org_id == "" then
    return nil, "X-Org-Id header is required"
  end

  if not biz_type or biz_type == "" then
    return nil, "X-Biz-Type header is required"
  end

  -- Default cache TTL is 300 seconds (5 minutes)
  cache_ttl = cache_ttl or 300

  -- Normalize optional dims for cache key (empty string when absent)
  local pid = project_id or ""
  local aid = application_id or ""
  local cache_key = region_id .. ":" .. org_id .. ":" .. biz_type .. ":" .. pid .. ":" .. aid

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

  -- Cache miss, acquire lock to query router service
  local lock_key = "router:" .. 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 = route_cache:get(cache_key)
      if cached then
        if locked then lock:unlock() end
        return cached, nil
      end
    end
  end

  local route_data, query_err = query_router_http(router_url, region_id, org_id, biz_type, project_id, application_id, timeout)
  if query_err or not route_data then
    if locked then lock:unlock() end
    return nil, query_err
  end

  -- Cache the result with configured TTL
  if cache_ttl > 0 then
    route_cache:set(cache_key, route_data, cache_ttl)
  end

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

return Router
