--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements.  See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You under the Apache License, Version 2.0
-- (the "License"); you may not use this file except in compliance with
-- the License.  You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

local type              = type
local pairs             = pairs
local tonumber          = tonumber
local ngx               = ngx
local re_find           = ngx.re.find
local fetch_local_conf  = require("apisix.core.config_local").local_conf
local try_read_attr     = require("apisix.core.table").try_read_attr
local deepcopy          = require("apisix.core.table").deepcopy
local log               = require("apisix.core.log")
local request           = require("apisix.core.request")
local response          = require("apisix.core.response")
local table             = require("apisix.core.table")

local _M = {}


local admin_api_version
local function enable_v3()
    if admin_api_version then
        if admin_api_version == "v3" then
            return true
        end

        if admin_api_version == "default" then
            return false
        end
    end

    local local_conf, err = fetch_local_conf()
    if not local_conf then
        admin_api_version = "default"
        log.error("failed to fetch local conf: ", err)
        return false
    end

    local api_ver = try_read_attr(local_conf, "deployment", "admin", "admin_api_version")
    if api_ver ~= "v3" then
        admin_api_version = "default"
        return false
    end

    admin_api_version = api_ver
    return true
end
_M.enable_v3 = enable_v3


function _M.to_v3(body, action)
    if not enable_v3() then
        body.action = action
    end
end


function _M.to_v3_list(body)
    if not enable_v3() then
        return
    end

    if body.node.dir then
        body.list = body.node.nodes
        body.node = nil
    end
end


local function sort(l, r)
    return l.createdIndex < r.createdIndex
end


local function pagination(body, args)
    args.page = tonumber(args.page)
    args.page_size = tonumber(args.page_size)
    if not args.page or not args.page_size then
        return
    end

    if args.page_size < 10 or args.page_size > 500 then
        return response.exit(400, "page_size must be between 10 and 500")
    end

    if not args.page or args.page < 1 then
        -- default page is 1
        args.page = 1
    end

    local list = body.list

    -- sort nodes by there createdIndex
    table.sort(list, sort)

    local to = args.page * args.page_size
    local from =  to - args.page_size + 1

    local res = table.new(20, 0)

    for i = from, to do
        if list[i] then
            res[i - from + 1] = list[i]
        end
    end

    body.list = res
end


local function _filter(item, args, resource)
    if not args.filter then
        return true
    end

    local filters, err = ngx.decode_args(args.filter or "", 100)
    if not filters then
        log.error("failed to decode filter args: ", err)
        return false
    end

    for key, value in pairs(filters) do
        if not resource.list_filter_fields[key] then
            log.warn("filter field '", key, "' is not supported by resource: ", resource.name)
            goto CONTINUE
        end

        if not item[key] then
            return false
        end

        if type(value) == "table" then
            value = value[#value] -- get the last value in the table
        end

        if item[key] ~= value then
            return false
        end

        ::CONTINUE::
    end

    return true
end


local function filter(body, args, resource)
    for i = #body.list, 1, -1 do
        local name_matched = true
        local label_matched = true
        local uri_matched = true
        if args.name then
            name_matched = false
            local matched = re_find(body.list[i].value.name, args.name, "jo")
            if matched then
                name_matched = true
            end
        end

        if args.label then
            label_matched = false
            if body.list[i].value.labels then
                for k, _ in pairs(body.list[i].value.labels) do
                    if k == args.label then
                        label_matched = true
                        break
                    end
                end
            end
        end

        if args.uri then
            uri_matched = false
            if body.list[i].value.uri then
                local matched = re_find(body.list[i].value.uri, args.uri, "jo")
                if matched then
                    uri_matched = true
                end
            end

            if body.list[i].value.uris then
                for _, uri in pairs(body.list[i].value.uris) do
                    if re_find(uri, args.uri, "jo") then
                        uri_matched = true
                        break
                    end
                end
            end
        end

        if not name_matched or not label_matched or not uri_matched
                            or not _filter(body.list[i].value, args, resource) then
            table.remove(body.list, i)
        end
    end
end


function _M.filter(body, resource)
    if not enable_v3() then
        return body
    end

    local args = request.get_uri_args()
    local processed_body = deepcopy(body)

    if processed_body.deleted then
        processed_body.node = nil
    end

    -- strip node wrapping for single query, create, and update scenarios.
    if processed_body.node then
        processed_body = processed_body.node
    end

    -- filter and paging logic for list query only
    if processed_body.list then
        filter(processed_body, args, resource)

        -- calculate the total amount of filtered data
        processed_body.total = processed_body.list and #processed_body.list or 0

        pagination(processed_body, args)

        -- remove the count field returned by etcd
        -- we don't need a field that reflects the length of the currently returned data,
        -- it doesn't make sense
        processed_body.count = nil
    end

    return processed_body
end


return _M
