local client = require "resty.websocket.client"
local helpers = require "spec.helpers"
local cjson = require "cjson"

for _, enable_buffering in ipairs({ false, true }) do
for _, strategy in helpers.each_strategy() do
  describe("Websockets [#" .. strategy .. "]" .. (enable_buffering and " with buffering on" or ""), function()
    lazy_setup(function()
      local bp = helpers.get_db_utils(strategy, {
        "routes",
        "services",
        enable_buffering and "plugins"
      }, enable_buffering and { "enable-buffering" } or nil)

      local service = bp.services:insert {
        name = "ws",
        path = "/ws",
      }

      bp.routes:insert {
        protocols   = { "http" },
        paths       = { "/up-ws" },
        service     = service,
        strip_path  = true,
      }

      if enable_buffering then
        bp.plugins:insert({
          name = "enable-buffering",
          protocols = { "http", "https", "grpc", "grpcs" },
          service = ngx.null,
          consumer = ngx.null,
          route = ngx.null,
        })
      end

      assert(helpers.start_kong({
        database   = strategy,
        plugins    = enable_buffering and "bundled,enable-buffering",
        nginx_conf = "spec/fixtures/custom_nginx.template",
      }))
    end)

    lazy_teardown(function()
      helpers.stop_kong(nil, true)
    end)

    local function open_socket(uri)
      local wc = assert(client:new())
      assert(wc:connect(uri))
      return wc
    end

    local function check_headers(host, port)
      local is_kong = (host == helpers.get_proxy_ip(false) and
                       port == helpers.get_proxy_port(false)) or
                      (host == helpers.get_proxy_ip(true) and
                       port == helpers.get_proxy_port(true))

      local encode_base64 = ngx.encode_base64
      local rand = math.random
      local char = string.char
      local sock = assert(ngx.socket.tcp())
      local path = is_kong and "/up-ws" or "/ws"

      assert(sock:connect(host, port))

      local bytes = char(rand(256) - 1, rand(256) - 1, rand(256) - 1,
                         rand(256) - 1, rand(256) - 1, rand(256) - 1,
                         rand(256) - 1, rand(256) - 1, rand(256) - 1,
                         rand(256) - 1, rand(256) - 1, rand(256) - 1,
                         rand(256) - 1, rand(256) - 1, rand(256) - 1,
                         rand(256) - 1)

      local key = encode_base64(bytes)
      local req = "GET " .. path .. " HTTP/1.1\r\nUpgrade: websocket\r\nHost: "
        .. host .. ":" .. port
        .. "\r\nSec-WebSocket-Key: " .. key
        .. "\r\nSec-WebSocket-Version: 13"
        .. "\r\nConnection: Upgrade\r\n\r\n"

      assert(sock:send(req))

      local header_reader = sock:receiveuntil("\r\n\r\n")
      local header = assert(header_reader())
      assert(sock:close())

      assert.equal(true, string.find(header, "HTTP/1.1 101 Switching Protocols") ~= nil, 1, true)
      assert.equal(true, string.find(header, "Connection: upgrade") ~= nil, 1, true)
      assert.equal(true, string.find(header, "Upgrade: websocket") ~= nil, 1, true)

      if is_kong then
        assert.equal(true, string.find(header, "Via: kong") ~= nil, 1, true)
      end
    end

    describe("headers", function()
      it("returns correct headers on handshake without Kong", function()
        check_headers("127.0.0.1", "15555")
      end)

      it("returns correct headers on handshake with Kong", function()
        check_headers(helpers.get_proxy_ip(false), helpers.get_proxy_port(false))
      end)
    end)

    describe("text", function()
      local function send_text_and_get_echo(uri)
        local payload = { message = "hello websocket" }
        local wc      = open_socket(uri)

        assert(wc:send_text(cjson.encode(payload)))
        local frame, typ, err = wc:recv_frame()
        assert.is_nil(wc.fatal)
        assert(frame, err)
        assert.equal("text", typ)
        assert.same(payload, cjson.decode(frame))

        assert(wc:send_close())
      end

      it("sends and gets text without Kong", function()
        send_text_and_get_echo("ws://127.0.0.1:15555/ws")
      end)

      it("sends and gets text with Kong", function()
        send_text_and_get_echo("ws://" .. helpers.get_proxy_ip(false) ..
                               ":" .. helpers.get_proxy_port(false) .. "/up-ws")
      end)

      it("sends and gets text with kong under HTTPS", function()
        send_text_and_get_echo("wss://" .. helpers.get_proxy_ip(true) ..
                               ":" .. helpers.get_proxy_port(true) .. "/up-ws")
      end)
    end)

    describe("ping pong", function()
      local function send_ping_and_get_pong(uri)
        local payload = { message = "give me a pong" }
        local wc      = open_socket(uri)

        assert(wc:send_ping(cjson.encode(payload)))
        local frame, typ, err = wc:recv_frame()
        assert.is_nil(wc.fatal)
        assert(frame, err)
        assert.equal("pong", typ)
        assert.same(payload, cjson.decode(frame))

        assert(wc:send_close())
      end

      it("plays ping-pong without Kong", function()
        send_ping_and_get_pong("ws://127.0.0.1:15555/ws")
      end)

      it("plays ping-pong with Kong", function()
        send_ping_and_get_pong("ws://" .. helpers.get_proxy_ip(false) ..
                               ":" .. helpers.get_proxy_port(false) .. "/up-ws")
      end)

      it("plays ping-pong with kong under HTTPS", function()
        send_ping_and_get_pong("wss://" .. helpers.get_proxy_ip(true) ..
                               ":" .. helpers.get_proxy_port(true) .. "/up-ws")
      end)
    end)
  end)
end
end
