{{- $namespace := .Release.Namespace }}
{{- $entraClientId := .Values.azure.clientId }}

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  namespace: {{ $namespace }}
  name: microsoft-identity-filter
spec:
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND
      listener:
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
            subFilter:
              name: envoy.filters.http.router
    patch:
      operation: INSERT_BEFORE
      value:
        name: envoy.lua.microsoft-identity-filter
        typed_config:
          "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
          inlineCode: |
            -- Constants
            local AAD_V1_ISSUER = "sts.windows.net"
            local AAD_V2_ISSUER = "login.microsoftonline.com"
            local entraClientId = "{{ $entraClientId }}"

            -- Helper function to log a table
            function tableToString(tbl, indent)
              if not indent then indent = 0 end
              if type(tbl) ~= 'table' then return tostring(tbl) end
              local lines = {}
              for k, v in pairs(tbl) do
                local formatting = string.rep("  ", indent) .. k .. ": "
                if type(v) == "table" then
                  table.insert(lines, formatting)
                  table.insert(lines, tableToString(v, indent + 1))
                else
                  table.insert(lines, formatting .. tostring(v))
                end
              end
              return table.concat(lines, "\n")
            end

            -- Function to log all headers
            function logAllHeaders(request_handle)
              local headers = request_handle:headers()
              for key, value in pairs(headers) do
                request_handle:logInfo("Header: " .. key .. " = " .. value)
              end
            end

            -- Process AAD v1 tokens
            function processAADV1Token(payload, request_handle)
              if payload["unique_name"] then
                request_handle:headers():add("x-user-id", payload["unique_name"])
                request_handle:logWarn("UC1-(AAD v1 User Token (sts.windows.net)): x-user-id set from 'unique_name' claim")
              elseif payload["oid"] then
                request_handle:headers():add("x-user-id", payload["appid"])
                request_handle:logWarn("UC1-(AAD v1 User Token (sts.windows.net)): x-user-id set from 'appid' claim as fallback")
              elseif payload["upn"] then
                request_handle:headers():add("x-user-id", payload["upn"])
                request_handle:logWarn("UC1-(AAD v1 User Token (sts.windows.net)): x-user-id set from 'upn' claim as fallback")
              else
                request_handle:logError("UC1-(AAD v1 User Token (sts.windows.net)): No valid claim for x-user-id found in AAD v1 token")
              end
            end

            -- Process AAD v2 tokens
            function processAADV2Token(payload, request_handle)
              if payload["unique_name"] then
                request_handle:headers():add("x-user-id", payload["unique_name"])
                request_handle:logWarn("UC4-(AAD v2 User Token (login.microsoftonline.com)): x-user-id set from 'unique_name' claim")
              elseif payload["oid"] then
                request_handle:headers():add("x-user-id", payload["oid"])
                request_handle:logWarn("UC4-(AAD v2 User Token (login.microsoftonline.com)): x-user-id set from 'oid' claim as fallback")
              elseif payload["azp"] then
                request_handle:headers():add("x-user-id", payload["azp"])
                request_handle:logWarn("UC4-(AAD v2 User Token (login.microsoftonline.com)): x-user-id set from 'azp' claim as fallback")
              else
                request_handle:logError("UC4-(AAD v2 User Token (login.microsoftonline.com)): No valid claim for x-user-id found in AAD v2 token")
              end
            end

            -- Main processing function
            function envoy_on_request(request_handle)
              -- Step 1: Remove existing headers
              request_handle:headers():remove("x-user-id")
              request_handle:headers():remove("x-app-id")
              request_handle:logWarn("x-user-id and x-app-id headers removed")

              -- Step 2: Retrieve JWT metadata
              local meta = request_handle:streamInfo():dynamicMetadata():get("envoy.filters.http.jwt_authn")
              if not meta or not meta["payload"] then
                request_handle:logError("No JWT metadata or payload found")
                return
              end
              local payload = meta["payload"]

              -- Step 3: Log raw payload for debugging
              request_handle:logDebug("JWT payload: " .. tableToString(payload))

              -- Step 4: Process audience (aud) claim
              local aud = payload["aud"]
              if aud then
                request_handle:headers():add("x-app-id", aud)
                request_handle:logDebug("x-app-id set from 'aud' claim: " .. aud)
                -- Special handling for audience "https://management.azure.com/"
                if aud == "https://management.azure.com/" then
                  local managedClientId = payload["appid"]
                  if managedClientId then
                    request_handle:headers():add("x-user-id", entraClientId)
                    request_handle:headers():replace("x-app-id", entraClientId)
                    request_handle:logWarn("UC7-(Management Audience): x-user-id and x-app-id set to 'appid' claim for management.azure.com audience")
                  else
                    request_handle:logError("No 'appid' claim found for management.azure.com audience")
                  end
                  return -- Exit early as we don't need further processing for this case
                end
              else
                request_handle:logError("No 'aud' claim found in payload")
              end

              -- Step 5: Process issuer (iss) for additional logic
              local iss = payload["iss"]
              if iss and string.find(iss, AAD_V1_ISSUER) then
                request_handle:logDebug("Processing AAD v1 token")
                processAADV1Token(payload, request_handle)
              elseif iss and string.find(iss, AAD_V2_ISSUER) then
                request_handle:logDebug("Processing AAD v2 token")
                processAADV2Token(payload, request_handle)
              else
                request_handle:logError("Unknown issuer: " .. (iss or "nil"))
              end

              -- Step 6: Log all headers before leaving the filter
              request_handle:logDebug("Logging all headers before leaving the filter:")
              logAllHeaders(request_handle)
            end