local cjson = require("cjson.safe")

local original_ngx = ngx
local function reset_ngx()
  _G.ngx = original_ngx
end

local function mock_ngx(mock)
  local _ngx = mock
  setmetatable(_ngx, { __index = ngx })
  _G.ngx = _ngx
end

local function mock_ngx_socket_tcp()
  local tcp_mock = {}
  stub(tcp_mock, "connect", true)
  stub(tcp_mock, "send", true)
  stub(tcp_mock, "close", true)

  local socket_mock = {}
  stub(socket_mock, "tcp", tcp_mock)
  mock_ngx({ socket = socket_mock })

  return tcp_mock
end

describe("Monitor", function()
  after_each(function()
    reset_ngx()
    package.loaded["monitor"] = nil
  end)

  it("extended batch size", function()
    mock_ngx({ var = {} })
    local monitor = require("monitor")
    monitor.set_metrics_max_batch_size(20000)

    for i = 1,20000,1 do
      monitor.call()
    end

    assert.equal(20000, #monitor.get_metrics_batch())
  end)

  it("batches metrics", function()
    mock_ngx({ var = {} })
    local monitor = require("monitor")

    for i = 1,10,1 do
      monitor.call()
    end

    assert.equal(10, #monitor.get_metrics_batch())
  end)

  describe("flush", function()
    it("short circuits when premature is true (when worker is shutting down)", function()
      local tcp_mock = mock_ngx_socket_tcp()
      mock_ngx({ var = {} })
      local monitor = require("monitor")

      for i = 1,10,1 do
        monitor.call()
      end
      monitor.flush(true)
      assert.stub(tcp_mock.connect).was_not_called()
    end)

    it("short circuits when there's no metrics batched", function()
      local tcp_mock = mock_ngx_socket_tcp()
      local monitor = require("monitor")

      monitor.flush()
      assert.stub(tcp_mock.connect).was_not_called()
    end)

    it("JSON encodes and sends the batched metrics", function()
      local tcp_mock = mock_ngx_socket_tcp()

      local ngx_var_mock = {
        host = "example.com",
        namespace = "default",
        ingress_name = "example",
        service_name = "http-svc",
        location_path = "/",

        request_method = "GET",
        status = "200",
        request_length = "256",
        request_time = "0.04",
        bytes_sent = "512",

        upstream_addr = "10.10.0.1",
        upstream_connect_time = "0.01",
        upstream_response_time = "0.02",
        upstream_response_length = "456",
        upstream_status = "200",
      }
      mock_ngx({ var = ngx_var_mock })
      local monitor = require("monitor")
      monitor.call()

      local ngx_var_mock1 = ngx_var_mock
      ngx_var_mock1.status = "201"
      ngx_var_mock1.request_method = "POST"
      mock_ngx({ var = ngx_var_mock })
      monitor.call()

      monitor.flush()

      local expected_payload = cjson.encode({
        {
          host = "example.com",
          namespace = "default",
          ingress = "example",
          service = "http-svc",
          path = "/",

          method = "GET",
          status = "200",
          requestLength = 256,
          requestTime = 0.04,
          responseLength = 512,

          upstreamLatency = 0.01,
          upstreamResponseTime = 0.02,
          upstreamResponseLength = 456,
        },
        {
          host = "example.com",
          namespace = "default",
          ingress = "example",
          service = "http-svc",
          path = "/",

          method = "POST",
          status = "201",
          requestLength = 256,
          requestTime = 0.04,
          responseLength = 512,

          upstreamLatency = 0.01,
          upstreamResponseTime = 0.02,
          upstreamResponseLength = 456,
        },
      })

      assert.stub(tcp_mock.connect).was_called_with(tcp_mock, "unix:/tmp/prometheus-nginx.socket")
      assert.stub(tcp_mock.send).was_called_with(tcp_mock, expected_payload)
      assert.stub(tcp_mock.close).was_called_with(tcp_mock)
    end)
  end)
end)
