local meta = require "kong.meta"
local helpers = require "spec.helpers"
local cjson = require "cjson"

describe("reports", function()
  local reports, bytes, err
  local expected_data = "version"
  local port = 8189

  lazy_setup(function()
    -- start the echo server
    assert(helpers.start_kong({
               nginx_conf = "spec/fixtures/custom_nginx.template",
               -- we don't actually use any stream proxy features in tcp_server,
               -- but this is needed in order to load the echo server defined at
               -- nginx_kong_test_tcp_echo_server_custom_inject_stream.lua
               stream_listen = helpers.get_proxy_ip(false) .. ":19000",
               -- to fix "Database needs bootstrapping or is older than Kong 1.0" in CI.
               database = "off",
               log_level = "info",
                      }))

    assert(helpers.is_echo_server_ready())
  end)

  lazy_teardown(function()
    helpers.stop_kong()
    helpers.echo_server_reset()
  end)

  before_each(function()
    package.loaded["kong.reports"] = nil
    reports = require "kong.reports"
  end)

  it("don't send report when anonymous_reports = false", function ()
    bytes, err = reports.send()
    assert.is_nil(bytes)
    assert.equal(err, "disabled")
  end)

  describe("when anonymous_reports = true, ", function ()
    lazy_setup(function()
      _G.kong = _G.kong or {}
      _G.kong.configuration = _G.kong.configuration or {}
      if not _G.kong.configuration.anonymous_reports then
        _G.kong.configuration = { anonymous_reports = true }
      end
    end)

    lazy_teardown(function()
      _G.kong.configuration.anonymous_reports = nil
    end)

    it("send reports", function()
      bytes, err = reports.send()
      assert.is_nil(bytes)
      assert.equal(err, "disabled")
    end)
  end)

  describe("toggle", function()
    before_each(function()
      reports.toggle(true)
    end)

    it("sends report over TCP[TLS]", function()
      bytes, err = reports.send("stub", {
        hello = "world",
        foo = "bar",
        baz = function() return "bat" end,
        foobar = function() return { foo = "bar" } end,
        bazbat = { baz = "bat" },
        nilval = function() return nil end,
      }, "127.0.0.1", port)

      assert.truthy(bytes>0)
      assert.is_nil(err)

      local res = helpers.get_echo_server_received_data(expected_data)

      assert.matches("^<14>", res)
      res = res:sub(5)
      assert.matches("cores=%d+", res)
      assert.matches("uname=[%w]+", res)
      assert.matches("version=" .. meta._VERSION, res, nil, true)
      assert.matches("hostname=[%w]+", res)
      assert.matches("foo=bar", res, nil, true)
      assert.matches("hello=world", res, nil, true)
      assert.matches("signal=stub", res, nil, true)
      assert.matches("baz=bat", res, nil, true)
      assert.not_matches("nilval", res, nil, true)
      assert.matches("foobar=" .. cjson.encode({ foo = "bar" }), res, nil, true)
      assert.matches("bazbat=" .. cjson.encode({ baz = "bat" }), res, nil, true)
    end)

    it("doesn't send if not enabled", function()
      reports.toggle(false)

      bytes, err = reports.send({
        foo = "bar"
      }, "127.0.0.1", port)
      assert.is_nil(bytes)
      assert.equal(err, "disabled")

      local res = helpers.get_echo_server_received_data(expected_data, 0.1)
      assert.equal("timeout", res)
    end)

    it("accepts custom immutable items", function()
      reports.toggle(true)

      reports.add_immutable_value("imm1", "fooval")
      reports.add_immutable_value("imm2", "barval")

      bytes, err = reports.send("stub", {k1 = "bazval"}, "127.0.0.1", port)
      assert.truthy(bytes > 0)
      assert.is_nil(err)

      local res = helpers.get_echo_server_received_data(expected_data)

      assert.matches("imm1=fooval", res)
      assert.matches("imm2=barval", res)
      assert.matches("k1=bazval", res)
    end)
  end)

  describe("configure_ping()", function()
    local conf_loader = require "kong.conf_loader"
    local function send_reports_and_check_result(reports, conf, port, matches)
      reports.configure_ping(conf)
      reports.send_ping("127.0.0.1", port)
      local res = helpers.get_echo_server_received_data(expected_data)

      for _,m in ipairs(matches) do
        assert.matches(m, res, nil, true)
      end
    end

    before_each(function()
      reports.toggle(true)
      reports._create_counter()
    end)

    describe("sends 'cluster_id'", function()
      it("uses mock value 123e4567-e89b-12d3-a456-426655440000", function()
        local conf = assert(conf_loader(nil, {
          database = "postgres",
        }))
        send_reports_and_check_result(
          reports,
          conf,
          port,
          {"cluster_id=123e4567-e89b-12d3-a456-426655440000"})
      end)
    end)

    describe("sends 'database'", function()
      it("postgres", function()
        local conf = assert(conf_loader(nil, {
          database = "postgres",
        }))
        send_reports_and_check_result(
          reports,
          conf,
          port,
          {"database=postgres"})
      end)

      it("off", function()
        local conf = assert(conf_loader(nil, {
          database = "off",
        }))
        send_reports_and_check_result(
          reports,
          conf,
          port,
          {"database=off"})
      end)
    end)

    describe("sends 'role'", function()
               it("traditional", function()
        local conf = assert(conf_loader(nil))
        send_reports_and_check_result(
          reports,
          conf,
          port,
          {"cluster_id=123e4567-e89b-12d3-a456-426655440000"})
      end)

      it("control_plane", function()
        local conf = assert(conf_loader(nil, {
          role = "control_plane",
          cluster_cert = "spec/fixtures/kong_spec.crt",
          cluster_cert_key = "spec/fixtures/kong_spec.key",
        }))
        send_reports_and_check_result(
          reports,
          conf,
          port,
          {"role=control_plane"})
      end)

      it("data_plane", function()
        local conf = assert(conf_loader(nil, {
          role = "data_plane",
          database = "off",
          cluster_cert = "spec/fixtures/kong_spec.crt",
          cluster_cert_key = "spec/fixtures/kong_spec.key",
        }))
        send_reports_and_check_result(
          reports,
          conf,
          port,
          {"role=data_plane"})
      end)
    end)

    describe("sends 'kic'", function()
      it("default (off)", function()
        local conf = assert(conf_loader(nil))
        send_reports_and_check_result(
          reports,
          conf,
          port,
          {"kic=false"})
      end)

      it("enabled", function()
        local conf = assert(conf_loader(nil, {
          kic = "on",
        }))
        send_reports_and_check_result(
          reports,
          conf,
          port,
          {"kic=true"})
      end)
    end)

    describe("sends '_admin' for 'admin_listen'", function()
      it("off", function()
        local conf = assert(conf_loader(nil, {
          admin_listen = "off",
        }))
        send_reports_and_check_result(
          reports,
          conf,
          port,
          {"_admin=0"})
      end)

      it("on", function()
        local conf = assert(conf_loader(nil, {
          admin_listen = "127.0.0.1:8001",
        }))
        send_reports_and_check_result(
          reports,
          conf,
          port,
          {"_admin=1"})
      end)
    end)

    describe("sends '_admin_gui' for 'admin_gui_listen'", function()
      it("off", function()
        local conf = assert(conf_loader(nil, {
          admin_gui_listen = "off",
        }))
        send_reports_and_check_result(
          reports,
          conf,
          port,
          {"_admin_gui=0"})
      end)

      it("on", function()
        local conf = assert(conf_loader(nil, {
          admin_gui_listen = "127.0.0.1:8001",
        }))
        send_reports_and_check_result(
          reports,
          conf,
          port,
          {"_admin_gui=1"})
      end)
    end)

    describe("sends '_proxy' for 'proxy_listen'", function()
      it("off", function()
        local conf = assert(conf_loader(nil, {
          proxy_listen = "off",
        }))
        send_reports_and_check_result(
          reports,
          conf,
          port,
          {"_proxy=0"})
      end)

      it("on", function()
        local conf = assert(conf_loader(nil, {
          proxy_listen = "127.0.0.1:8000",
        }))
        send_reports_and_check_result(
          reports,
          conf,
          port,
          {"_proxy=1"})
      end)
    end)

    describe("sends '_stream' for 'stream_listen'", function()
      it("off", function()
        local conf = assert(conf_loader(nil, {
          stream_listen = "off",
        }))
        send_reports_and_check_result(
          reports,
          conf,
          port,
          {"_stream=0"})
      end)

      it("on", function()
        local conf = assert(conf_loader(nil, {
          stream_listen = "127.0.0.1:8000",
        }))
        send_reports_and_check_result(
          reports,
          conf,
          port,
          {"_stream=1"})
      end)
    end)

    it("default configuration ping contents", function()
        local conf = assert(conf_loader())
        send_reports_and_check_result(
          reports,
          conf,
          port,
          {"database=" .. helpers.test_conf.database,
           "_admin=1",
           "_proxy=1",
           "_stream=0"
        })
    end)

    it("sends 'new_dns_client'", function()
      local conf = assert(conf_loader())
      conf.new_dns_client = true
      send_reports_and_check_result(
        reports,
        conf,
        port,
        {"new_dns_client=true"}
      )

      conf.new_dns_client = false
      send_reports_and_check_result(
        reports,
        conf,
        port,
        {"new_dns_client=false"}
      )
    end)
  end)

  describe("retrieve_redis_version()", function()
    lazy_setup(function()
      stub(ngx, "log")
    end)

    lazy_teardown(function()
      ngx.log:revert() -- luacheck: ignore
    end)

    before_each(function()
      reports.toggle(true)
      reports._create_counter()
    end)

    it("does not query Redis if not enabled", function()
      reports.toggle(false)

      local red_mock = {
        info = function() end,
      }

      local s = spy.on(red_mock, "info")

      reports.retrieve_redis_version(red_mock)
      assert.spy(s).was_not_called()
    end)

    it("queries Redis if enabled", function()
      local red_mock = {
        info = function()
          return "redis_version:2.4.5\r\nredis_git_sha1:e09e31b1"
        end,
      }

      local s = spy.on(red_mock, "info")

      reports.retrieve_redis_version(red_mock)
      assert.spy(s).was_called_with(red_mock, "server")
    end)

    it("queries Redis only once", function()
      local red_mock = {
        info = function()
          return "redis_version:2.4.5\r\nredis_git_sha1:e09e31b1"
        end,
      }

      local s = spy.on(red_mock, "info")

      reports.retrieve_redis_version(red_mock)
      reports.retrieve_redis_version(red_mock)
      reports.retrieve_redis_version(red_mock)
      assert.spy(s).was_called(1)
    end)

    it("retrieves major.minor version", function()
      local red_mock = {
        info = function()
          return "redis_version:2.4.5\r\nredis_git_sha1:e09e31b1"
        end,
      }

      reports.retrieve_redis_version(red_mock)
      assert.equal("2.4", reports.get_ping_value("redis_version"))
    end)

    it("retrieves 'unknown' when the version could not be retrieved (1/3)", function()
      local red_mock = {
        info = function()
          return nil
        end,
      }

      reports.retrieve_redis_version(red_mock)
      assert.equal("unknown", reports.get_ping_value("redis_version"))
    end)

    it("retrieves 'unknown' when the version could not be retrieved (2/3)", function()
      local red_mock = {
        info = function()
          return ngx.null
        end,
      }

      reports.retrieve_redis_version(red_mock)
      assert.equal("unknown", reports.get_ping_value("redis_version"))
    end)

    it("retrieves 'unknown' when the version could not be retrieved (3/3)", function()
      local red_mock = {
        info = function()
          return "hello world"
        end,
      }

      reports.retrieve_redis_version(red_mock)
      assert.equal("unknown", reports.get_ping_value("redis_version"))
    end)
  end)
end)
