local ipairs = ipairs
local type = type


local log_warn_message, _AI_PROVIDER_INCOMPATIBLE
do
  local ngx_log = ngx.log
  local ngx_WARN = ngx.WARN
  local fmt = string.format

  local KONG_VERSION = require("kong.meta").version

  local _log_prefix = "[clustering] "

  log_warn_message = function(hint, action, dp_version, log_suffix)
    local msg = fmt("Kong Gateway v%s %s " ..
                    "which is incompatible with dataplane version %s " ..
                    "and will %s.",
                    KONG_VERSION, hint, dp_version, action)
    ngx_log(ngx_WARN, _log_prefix, msg, log_suffix)
  end

  local _AI_PROVIDERS_ADDED = {
    [3009000000] = {
      "huggingface",
    },
    [3008000000] = {
      "gemini",
      "bedrock",
    },
  }

  _AI_PROVIDER_INCOMPATIBLE = function(provider, ver)
    -- Check all versions >= ver to see if provider was added in any of them
    for version, providers in pairs(_AI_PROVIDERS_ADDED) do
      if version >= ver then
        for _, v in ipairs(providers) do
          if v == provider then
            return true
          end
        end
      end
    end

    return false
  end
end

local compatible_checkers = {
  { 3009000000, --[[ 3.9.0.0 ]]
    function (config_table, dp_version, log_suffix)
      local has_update
      for _, plugin in ipairs(config_table.plugins or {}) do
        if plugin.name == 'ai-proxy' then
          local config = plugin.config
          if _AI_PROVIDER_INCOMPATIBLE(config.model.provider, 3009000000) then
            log_warn_message('configures ' .. plugin.name .. ' plugin with' ..
            ' "openai preserve mode", because ' .. config.model.provider .. ' provider ' ..
            ' is not supported in this release',
            dp_version, log_suffix)

            config.model.provider = "openai"
            config.route_type = "preserve"
            
            -- Remove provider-specific options for huggingface
            if config.model.options and config.model.options.huggingface then
              config.model.options.huggingface = nil
            end

            has_update = true
          end
        end

        if plugin.name == 'ai-request-transformer' then
          local config = plugin.config
          if _AI_PROVIDER_INCOMPATIBLE(config.llm.model.provider, 3009000000) then
            log_warn_message('configures ' .. plugin.name .. ' plugin with' ..
            ' "openai preserve mode", because ' .. config.llm.model.provider .. ' provider ' ..
            ' is not supported in this release',
            dp_version, log_suffix)

            config.llm.model.provider = "openai"
            
            -- Remove provider-specific options for huggingface
            if config.llm.model.options and config.llm.model.options.huggingface then
              config.llm.model.options.huggingface = nil
            end

            has_update = true
          end
        end

        if plugin.name == 'ai-response-transformer' then
          local config = plugin.config
          if _AI_PROVIDER_INCOMPATIBLE(config.llm.model.provider, 3009000000) then
            log_warn_message('configures ' .. plugin.name .. ' plugin with' ..
            ' "openai preserve mode", because ' .. config.llm.model.provider .. ' provider ' ..
            ' is not supported in this release',
            dp_version, log_suffix)

            config.llm.model.provider = "openai"
            
            -- Remove provider-specific options for huggingface
            if config.llm.model.options and config.llm.model.options.huggingface then
              config.llm.model.options.huggingface = nil
            end

            has_update = true
          end
        end
      end

      return has_update
    end
  },
  { 3008000000, --[[ 3.8.0.0 ]]
    function (config_table, dp_version, log_suffix)
      local has_update
      for _, plugin in ipairs(config_table.plugins or {}) do
        if plugin.name == 'acme' then
          local config = plugin.config
          if config.storage_config.redis.username ~= nil then
            log_warn_message('configures ' .. plugin.name .. ' plugin with redis username',
              'not work in this release',
              dp_version, log_suffix)
          end
        end

        if plugin.name == 'aws-lambda' then
          local config = plugin.config
          if config.aws_sts_endpoint_url ~= nil then
            config.aws_sts_endpoint_url = nil
            has_update = true
            log_warn_message('configures ' .. plugin.name .. ' plugin with aws_sts_endpoint_url',
              'will be removed.',
              dp_version, log_suffix)
          end
        end

        if plugin.name == 'ai-proxy' then
          local config = plugin.config
          if _AI_PROVIDER_INCOMPATIBLE(config.model.provider, 3008000000) then
            log_warn_message('configures ' .. plugin.name .. ' plugin with' ..
            ' "openai preserve mode", because ' .. config.model.provider .. ' provider ' ..
            ' is not supported in this release',
            dp_version, log_suffix)

            config.model.provider = "openai"
            config.route_type = "preserve"

            has_update = true
          end

          if config.model.provider == "mistral" and (
            not config.model.options or
            config.model.options == ngx.null or
            not config.model.options.upstream_url or
            config.model.options.upstream_url == ngx.null) then

            log_warn_message('configures ' .. plugin.name .. ' plugin with' ..
              ' mistral provider uses fallback upstream_url for managed serivice' ..
              dp_version, log_suffix)

            config.model.options = config.model.options or {}
            config.model.options.upstream_url = "https://api.mistral.ai:443"
            has_update = true
          end

        end

        if plugin.name == 'ai-request-transformer' then
          local config = plugin.config
          if _AI_PROVIDER_INCOMPATIBLE(config.llm.model.provider, 3008000000) then
            log_warn_message('configures ' .. plugin.name .. ' plugin with' ..
            ' "openai preserve mode", because ' .. config.llm.model.provider .. ' provider ' ..
            ' is not supported in this release',
            dp_version, log_suffix)

            config.llm.model.provider = "openai"

            has_update = true
          end
        end

        if plugin.name == 'ai-response-transformer' then
          local config = plugin.config
          if _AI_PROVIDER_INCOMPATIBLE(config.llm.model.provider, 3008000000) then
            log_warn_message('configures ' .. plugin.name .. ' plugin with' ..
            ' "openai preserve mode", because ' .. config.llm.model.provider .. ' provider ' ..
            ' is not supported in this release',
            dp_version, log_suffix)

            config.llm.model.provider = "openai"

            has_update = true
          end
        end
      end

      return has_update
    end
  },
  { 3007000000, --[[ 3.7.0.0 ]]
    function(config_table, dp_version, log_suffix)
      local has_update

      for _, plugin in ipairs(config_table.plugins or {}) do
        if plugin.name == 'ai-proxy' then
          local config = plugin.config
          if config.route_type == "preserve" then
            config.route_type = "llm/v1/chat"
            log_warn_message('configures ' .. plugin.name .. ' plugin with' ..
                              ' route_type == "llm/v1/chat", because preserve' ..
                              ' mode is not supported in this release',
                              dp_version, log_suffix)
            has_update = true
          end
        end
      end

      return has_update
    end,
  },
  { 3006000000, --[[ 3.6.0.0 ]]
    function(config_table, dp_version, log_suffix)
      local has_update
      local redis_plugins_update = {
        acme = require("kong.plugins.acme.clustering.compat.redis_translation").adapter,
        ['rate-limiting'] = require("kong.plugins.rate-limiting.clustering.compat.redis_translation").adapter,
        ['response-ratelimiting'] = require("kong.plugins.response-ratelimiting.clustering.compat.redis_translation").adapter
      }

      for _, plugin in ipairs(config_table.plugins or {}) do
        local adapt_fn = redis_plugins_update[plugin.name]
        if adapt_fn and type(adapt_fn) == "function" then
          has_update = adapt_fn(plugin.config)
          if has_update then
            log_warn_message('adapts ' .. plugin.name .. ' plugin redis configuration to older version',
            'revert to older schema',
            dp_version, log_suffix)
          end
        end
      end

      return has_update
    end,
  },

  { 3005000000, --[[ 3.5.0.0 ]]
    function(config_table, dp_version, log_suffix)
      local has_update

      for _, plugin in ipairs(config_table.plugins or {}) do
        if plugin.name == 'opentelemetry' or plugin.name == 'zipkin' then
          local config = plugin.config
          if config.header_type == 'gcp' then
            config.header_type = 'preserve'
            log_warn_message('configures ' .. plugin.name .. ' plugin with:' ..
                             ' header_type == gcp',
                             'overwritten with default value `preserve`',
                             dp_version, log_suffix)
            has_update = true
          end
        end

        if plugin.name == 'zipkin' then
          local config = plugin.config
          if config.default_header_type == 'gcp' then
            config.default_header_type = 'b3'
            log_warn_message('configures ' .. plugin.name .. ' plugin with:' ..
                             ' default_header_type == gcp',
                             'overwritten with default value `b3`',
                             dp_version, log_suffix)
            has_update = true
          end
        end
      end

      return has_update
    end,
  },

  { 3004000000, --[[ 3.4.0.0 ]]
    function(config_table, dp_version, log_suffix)
      local has_update

      for _, plugin in ipairs(config_table.plugins or {}) do
        if plugin.name == 'opentelemetry' or plugin.name == 'zipkin' then
          local config = plugin.config
          if config.header_type == 'aws' then
            config.header_type = 'preserve'
            log_warn_message('configures ' .. plugin.name .. ' plugin with:' ..
                             ' header_type == aws',
                             'overwritten with default value `preserve`',
                             dp_version, log_suffix)
            has_update = true
          end
        end

        if plugin.name == 'zipkin' then
          local config = plugin.config
          if config.default_header_type == 'aws' then
            config.default_header_type = 'b3'
            log_warn_message('configures ' .. plugin.name .. ' plugin with:' ..
                             ' default_header_type == aws',
                             'overwritten with default value `b3`',
                             dp_version, log_suffix)
            has_update = true
          end
        end
      end

      return has_update
    end,
  },

  { 3003000000, --[[ 3.3.0.0 ]]
    function(config_table, dp_version, log_suffix)
      local has_update

      -- Support legacy queueing parameters for plugins that used queues prior to 3.3.  `retry_count` has been
      -- completely removed, so we always supply the default of 10 as that provides the same behavior as with a
      -- pre 3.3 CP.  The other queueing related legacy parameters can be determined from the new queue
      -- configuration table.
      for _, plugin in ipairs(config_table.plugins or {}) do
        local config = plugin.config

        if plugin.name == 'statsd' or plugin.name == 'datadog' then
          if type(config.retry_count) ~= "number" then
            config.retry_count = 10
            has_update = true
          end

          if type(config.queue_size) ~= "number" then
            if config.queue and type(config.queue.max_batch_size) == "number" then
              config.queue_size = config.queue.max_batch_size
              has_update = true

            else
              config.queue_size = 1
              has_update = true
            end
          end

          if type(config.flush_timeout) ~= "number" then
            if config.queue and type(config.queue.max_coalescing_delay) == "number" then
              config.flush_timeout = config.queue.max_coalescing_delay
              has_update = true

            else
              config.flush_timeout = 2
              has_update = true
            end
          end

        elseif plugin.name == 'opentelemetry' then

          if type(config.batch_span_count) ~= "number" then
            if config.queue and type(config.queue.max_batch_size) == "number" then
              config.batch_span_count = config.queue.max_batch_size
              has_update = true

            else
              config.batch_span_count = 200
              has_update = true
            end
          end

          if type(config.batch_flush_delay) ~= "number" then
            if config.queue and type(config.queue.max_coalescing_delay) == "number" then
              config.batch_flush_delay = config.queue.max_coalescing_delay
              has_update = true

            else
              config.batch_flush_delay = 3
              has_update = true
            end
          end
        end -- if plugin.name
      end   -- for

      return has_update
    end,
  },

  { 3003000000, --[[ 3.3.0.0 ]]
    function(config_table, dp_version, log_suffix)
      local has_update

      for _, config_entity in ipairs(config_table.vaults or {}) do
        if config_entity.name == "env" and type(config_entity.config) == "table" then
          local config = config_entity.config
          local prefix = config.prefix

          if type(prefix) == "string" then
            local new_prefix = prefix:gsub("-", "_")
            if new_prefix ~= prefix then
              config.prefix = new_prefix
              has_update = true
            end
          end
        end
      end   -- for

      return has_update
    end,
  },

  { 3003000000, --[[ 3.3.0.0 ]]
    function(config_table, dp_version, log_suffix)
      -- remove updated_at field for core entities ca_certificates, certificates, consumers,
      -- targets, upstreams, plugins, workspaces, clustering_data_planes and snis
      local entity_names = {
        "ca_certificates", "certificates", "consumers", "targets", "upstreams",
        "plugins", "workspaces", "snis",
        }

      local has_update
      local updated_entities = {}

      for _, name in ipairs(entity_names) do
        for _, config_entity in ipairs(config_table[name] or {}) do
          if config_entity["updated_at"] then

            config_entity["updated_at"] = nil

            has_update = true

            if not updated_entities[name] then
              log_warn_message("contains configuration '" .. name .. ".updated_at'",
                               "be removed",
                               dp_version,
                               log_suffix)

              updated_entities[name] = true
            end
          end
        end
      end

      return has_update
    end
  },

  { 3002000000, --[[ 3.2.0.0 ]]
    function(config_table, dp_version, log_suffix)
      local config_services = config_table["services"]
      if not config_services  then
        return nil
      end

      local has_update
      for _, t in ipairs(config_services) do
        if t["protocol"] == "tls" then
          if t["client_certificate"] or t["tls_verify"] or
             t["tls_verify_depth"]   or t["ca_certificates"]
          then

            t["client_certificate"] = nil
            t["tls_verify"] = nil
            t["tls_verify_depth"] = nil
            t["ca_certificates"] = nil

            has_update = true
          end
        end
      end

      if has_update then
        log_warn_message("tls protocol service contains configuration 'service.client_certificate'" ..
                         "or 'service.tls_verify' or 'service.tls_verify_depth' or 'service.ca_certificates'",
                         "be removed",
                         dp_version,
                         log_suffix)
      end

      return has_update
    end
  },

  { 3002000000, --[[ 3.2.0.0 ]]
    function(config_table, dp_version, log_suffix)
      local config_upstreams = config_table["upstreams"]
      if not config_upstreams  then
        return nil
      end

      local has_update
      for _, t in ipairs(config_upstreams) do
        if t["algorithm"] == "latency" then
          t["algorithm"] = "round-robin"
          has_update = true
        end
      end

      if has_update then
        log_warn_message("configuration 'upstream.algorithm' contains 'latency' option",
                         "fall back to 'round-robin'",
                         dp_version,
                         log_suffix)
      end

      return has_update
    end
  },

  { 3002000000, --[[ 3.2.0.0 ]]
    function(config_table, dp_version, log_suffix)
      local config_plugins = config_table["plugins"]
      if not config_plugins then
        return nil
      end

      local has_update
      for _, plugin in ipairs(config_plugins) do
        if plugin["instance_name"] ~= nil then
          plugin["instance_name"] = nil
          has_update = true
        end
      end

      if has_update then
        log_warn_message("contains configuration 'plugin.instance_name'",
                         "be removed",
                         dp_version,
                         log_suffix)
      end

      return has_update
    end
  },

  { 3001000000, --[[ 3.1.0.0 ]]
    function(config_table, dp_version, log_suffix)
      local config_upstreams = config_table["upstreams"]
      if not config_upstreams then
        return nil
      end

      local has_update
      for _, t in ipairs(config_upstreams) do
        if t["use_srv_name"] ~= nil then
          t["use_srv_name"] = nil
          has_update = true
        end
      end

      if has_update then
        log_warn_message("contains configuration 'upstream.use_srv_name'",
                         "be removed",
                         dp_version,
                         log_suffix)
      end

      return has_update
    end
  },
}


return compatible_checkers
