local cjson   = require "cjson"
local helpers = require "spec.helpers"
local uuid   = require("kong.tools.uuid").uuid


local POLL_INTERVAL = 0.3


local function assert_admin_2_wait(request, res_status, res_not_message)
  local ret
  helpers.wait_until(function()
    local admin_client_2 = helpers.http_client("127.0.0.1", 9001)
    finally(function()
      admin_client_2:close()
    end)

    local res = admin_client_2:send(request)
    if not res then
      return false
    end
    if res.status ~= res_status then
      return false
    end
    local body = res:read_body()
    if not body then
      return false
    end
    local json = cjson.decode(body)
    if res_not_message then
      if not json.message:match("^[%w-]+$") then
        return false
      end
      if json.message == res_not_message then
        return false
      end
    end
    ret = json
    return true
  end, 10)
  return ret
end


for _, strategy in helpers.each_strategy() do
  describe("plugins iterator with db [#" .. strategy .. "]", function()

    local admin_client_1
    local admin_client_2

    local proxy_client_1
    local proxy_client_2

    local service_fixture

    lazy_setup(function()
      local bp = helpers.get_db_utils(strategy, {
        "routes",
        "services",
        "plugins",
        "certificates",
      })

      -- insert single fixture Service
      service_fixture = bp.services:insert()

      assert(helpers.start_kong {
        log_level             = "debug",
        prefix                = "servroot1",
        database              = strategy,
        proxy_listen          = "0.0.0.0:8000, 0.0.0.0:8443 ssl",
        admin_listen          = "0.0.0.0:8001",
        db_update_frequency   = POLL_INTERVAL,
        nginx_conf            = "spec/fixtures/custom_nginx.template",
      })

      assert(helpers.start_kong {
        log_level             = "debug",
        prefix                = "servroot2",
        database              = strategy,
        proxy_listen          = "0.0.0.0:9000, 0.0.0.0:9443 ssl",
        admin_listen          = "0.0.0.0:9001",
        db_update_frequency   = POLL_INTERVAL,
      })

      local admin_client = helpers.http_client("127.0.0.1", 8001)
      local admin_res = assert(admin_client:send {
        method  = "POST",
        path    = "/routes",
        body    = {
          protocols = { "http" },
          hosts     = { "dummy.test" },
          service   = {
            id = service_fixture.id,
          }
        },
        headers = {
          ["Content-Type"] = "application/json",
        },
      })
      assert.res_status(201, admin_res)
      admin_client:close()
    end)

    lazy_teardown(function()
      helpers.stop_kong("servroot1")
      helpers.stop_kong("servroot2")
    end)

    before_each(function()
      admin_client_1 = helpers.http_client("127.0.0.1", 8001)
      admin_client_2 = helpers.http_client("127.0.0.1", 9001)
      proxy_client_1 = helpers.http_client("127.0.0.1", 8000)
      proxy_client_2 = helpers.http_client("127.0.0.1", 9000)
    end)

    after_each(function()
      admin_client_1:close()
      admin_client_2:close()
      proxy_client_1:close()
      proxy_client_2:close()
    end)

    describe("plugins_iterator:version", function()
      local service_plugin_id

      it("is created at startup", function()
        local admin_res_1 = assert(admin_client_1:send {
          method = "GET",
          path   = "/cache/plugins_iterator:version",
        })
        local body_1 = assert.res_status(200, admin_res_1)
        local msg_1  = cjson.decode(body_1)

        local admin_res_2 = assert(admin_client_2:send {
          method = "GET",
          path   = "/cache/plugins_iterator:version",
        })
        local body_2 = assert.res_status(200, admin_res_2)
        local msg_2  = cjson.decode(body_2)

        assert.equal("init", msg_1.message)
        assert.equal("init", msg_2.message)
      end)

      it("changes on plugin creation", function()
        local admin_res_before = admin_client_2:get("/cache/plugins_iterator:version")
        local body_before = assert.res_status(200, admin_res_before)
        local msg_before  = cjson.decode(body_before)
        assert.matches("^[%w-]+$", msg_before.message)

        -- create Plugin
        local admin_res_plugin = assert(admin_client_1:send {
          method = "POST",
          path   = "/plugins",
          body   = {
            name    = "dummy",
            service = { id = service_fixture.id },
          },
          headers = {
            ["Content-Type"] = "application/json",
          },
        })
        local body = assert.res_status(201, admin_res_plugin)
        local plugin = cjson.decode(body)
        service_plugin_id = plugin.id

        assert_admin_2_wait({
          method = "GET",
          path = "/cache/plugins_iterator:version",
        }, 200, msg_before.message)
      end)

      it("changes on proxied request or timer", function()
        -- issue a request
        local res_1 = assert(proxy_client_1:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            host = "dummy.test",
          }
        })
        assert.res_status(200, res_1)

        local admin_res_1 = assert(admin_client_1:send {
          method = "GET",
          path   = "/cache/plugins_iterator:version",
        })
        local body_1 = assert.res_status(200, admin_res_1)
        local msg_1  = cjson.decode(body_1)

        assert.matches("^[%w-]+$", msg_1.message)

        -- each node has their own version
        local msg_2 = assert_admin_2_wait({
          method = "GET",
          path = "/cache/plugins_iterator:version",
        }, 200)

        -- check that node 2 was already up to date via its timer
        local res_2 = assert(proxy_client_2:send {
          method  = "GET",
          path    = "/status/200",
          headers = {
            host = "dummy.test",
          }
        })
        assert.res_status(200, res_2)

        local admin_res_3 = assert(admin_client_2:send {
          method = "GET",
          path   = "/cache/plugins_iterator:version",
        })
        local body_3 = assert.res_status(200, admin_res_3)
        local msg_3  = cjson.decode(body_3)
        assert.matches("^[%w-]+$", msg_3.message)

        -- no version change
        assert.equals(msg_2.message, msg_3.message)
      end)

      it("changes on plugin PATCH", function()
        local admin_res_before = admin_client_2:get("/cache/plugins_iterator:version")
        local body_before = assert.res_status(200, admin_res_before)
        local msg_before  = cjson.decode(body_before)
        assert.matches("^[%w-]+$", msg_before.message)

        local admin_res_plugin = assert(admin_client_1:send {
          method = "PATCH",
          path   = "/plugins/" .. service_plugin_id,
          body   = {
            config = {
              resp_header_value = "2"
            }
          },
          headers = {
            ["Content-Type"] = "application/json",
          },
        })
        assert.res_status(200, admin_res_plugin)

        -- the version has changed
        assert_admin_2_wait({
          method = "GET",
          path = "/cache/plugins_iterator:version",
        }, 200, msg_before.message)
      end)

      it("changes on plugin DELETE", function()
        local msg_before = assert_admin_2_wait({
          method = "GET",
          path = "/cache/plugins_iterator:version",
        }, 200)

        local admin_res_plugin = assert(admin_client_1:send {
          method = "DELETE",
          path   = "/plugins/" .. service_plugin_id,
        })
        assert.res_status(204, admin_res_plugin)

        -- the version has changed
        assert_admin_2_wait({
          method = "GET",
          path = "/cache/plugins_iterator:version",
        }, 200, msg_before.message)
      end)

      it("changes on plugin PUT", function()
        local msg_before = assert_admin_2_wait({
          method = "GET",
          path = "/cache/plugins_iterator:version",
        }, 200)

        -- A regression test for https://github.com/Kong/kong/issues/4191
        local admin_res_plugin = assert(admin_client_1:send {
          method = "PUT",
          path   = "/plugins/" .. uuid(),
          body   = {
            name    = "dummy",
            service = { id = service_fixture.id },
          },
          headers = {
            ["Content-Type"] = "application/json",
          },
        })
        assert.res_status(200, admin_res_plugin)

        -- the version has changed
        assert_admin_2_wait({
          method = "GET",
          path = "/cache/plugins_iterator:version",
        }, 200, msg_before.message)
      end)
    end)
  end)
end
