local helpers = require "spec.helpers"
local cjson = require "cjson"
local merge = kong.table.merge
local fmt = string.format


for _, strategy in helpers.all_strategies() do
  describe("db.keys #" .. strategy, function()
    local init_key_set, init_pem_key, pem_pub, pem_priv, jwk
    local bp, db

    lazy_setup(function()
      helpers.setenv("JWK_SECRET", "wowsuchsecret")

      bp, db = helpers.get_db_utils(strategy ~= "off" and strategy or nil, {
        "vaults",
        "keys",
        "key_sets"
      })

      init_key_set = assert(bp.key_sets:insert {
        name = "testset",
      })

      local jwk_pub, jwk_priv = helpers.generate_keys("JWK")
      pem_pub, pem_priv = helpers.generate_keys("PEM")

      jwk = merge(cjson.decode(jwk_pub), cjson.decode(jwk_priv))
    end)

    after_each(function()
      db:truncate("keys")
    end)

    lazy_teardown(function()
      db:truncate("key_sets")
    end)

    it(":select returns an item [jwk]", function()
      local key, err = assert(bp.keys:insert {
        name = "testjwk",
        set = init_key_set,
        kid = jwk.kid,
        jwk = cjson.encode(jwk)
      })
      assert(key)
      assert.is_nil(err)
      local key_o, s_err = db.keys:select(key)
      assert.is_nil(s_err)
      assert.same("string", type(key_o.jwk))
    end)

    it(":select returns an item [pem]", function()
      init_pem_key = assert(bp.keys:insert {
        name = "testpem",
        set = init_key_set,
        kid = "456",
        pem = {
          public_key = pem_pub,
          private_key = pem_priv
        }
      })
      local key_o, err = db.keys:select(init_pem_key)
      assert.is_nil(err)
      assert.same('456', key_o.kid)
      assert.same(pem_priv, key_o.pem.private_key)
      assert.same(pem_pub, key_o.pem.public_key)
    end)

    it(":cache_key", function()
      local cache_key, err = db.keys:cache_key({kid = "456", set = {id = init_key_set.id}})
      assert.is_nil(err)
      assert.equal(fmt("keys:456:%s", init_key_set.id), cache_key)
    end)

    it(":cache_key no set present", function()
      local cache_key, err = db.keys:cache_key({kid = "123"})
      assert.is_nil(err)
      assert.equal("keys:123:", cache_key)
    end)

    it(":cache_key invalid set type", function()
      local cache_key, err = db.keys:cache_key({kid = "123", set = ""})
      assert.is_nil(err)
      assert.equal("keys:123:", cache_key)
    end)

    it(":cache_key must handle missing id field", function()
      local cache_key, err = db.keys:cache_key({kid = "123", set = { }})
      assert.is_nil(err)
      assert.equal("keys:123:", cache_key)
    end)

    it(":insert handles field vault references ", function()
      local reference = "{vault://env/jwk_secret}"
      local ref, insert_err = db.keys:insert {
        name = "vault references",
        set = init_key_set,
        kid = "1",
        jwk = reference
      }
      assert.is_nil(insert_err)
      assert.same(ref["$refs"]["jwk"], reference)
      assert.same(ref.jwk, "wowsuchsecret")
    end)

    it(":insert handles field private_key when passing a vault reference", function()
      local reference = "{vault://env/jwk_secret}"
      local ref, insert_err = db.keys:insert {
        name = "vault references",
        set = init_key_set,
        kid = "1",
        pem = { private_key = reference, public_key = pem_pub }
      }
      assert.is_nil(insert_err)
      assert.same(ref.pem["$refs"]["private_key"], reference)
      assert.same(ref.pem["private_key"], "wowsuchsecret")
    end)

    it(":insert handles field public_key when passing a vault reference", function()
      local reference = "{vault://env/jwk_secret}"
      local ref, insert_err = db.keys:insert {
        name = "vault references",
        set = init_key_set,
        kid = "1",
        pem = { private_key = pem_priv, public_key = reference}
      }
      assert.is_nil(insert_err)
      assert.same(ref.pem["$refs"]["public_key"], reference)
      assert.same(ref.pem["public_key"], "wowsuchsecret")
    end)

    it("kid is unique accross sets", function()
      local test2, err = db.key_sets:insert {
        name = "test2"
      }
      assert.is_nil(err)
      assert.is_not_nil(test2)
      local key, insert_err = db.keys:insert {
        name = "each_test",
        set = init_key_set,
        kid = "999",
        pem = { private_key = pem_priv, public_key = pem_pub }
      }
      assert.is_nil(insert_err)
      assert.is_not_nil(key)
      -- inserting a key with the same kid in a different keyset.
      -- this should not raise a validation error
      local key2, insert2_err = db.keys:insert {
        name = "each_test_1",
        set = test2,
        kid = "999",
        pem = { private_key = pem_priv, public_key = pem_pub }
      }
      assert.is_nil(insert2_err)
      assert.is_not_nil(key2)
    end)


    it(":get_pubkey and :get_privkey [pem]", function()
      local pem_t, err = db.keys:insert {
        name = "pem_key",
        set = init_key_set,
        kid = "999",
        pem = { private_key = pem_priv, public_key = pem_pub }
      }
      assert.is_nil(err)
      assert(pem_t)

      local pem_pub_t, g_err = db.keys:get_pubkey(pem_t)
      assert.is_nil(g_err)
      assert.matches("-----BEGIN PUBLIC KEY", pem_pub_t)

      local pem_priv, p_err = db.keys:get_privkey(pem_t)
      assert.is_nil(p_err)
      assert.matches("-----BEGIN PRIVATE KEY", pem_priv)
    end)

    it(":get_pubkey and :get_privkey [jwk]", function()
      local jwk_t, _ = db.keys:insert {
        name = "jwk_key",
        set = init_key_set,
        kid = jwk.kid,
        jwk = cjson.encode(jwk)
      }
      assert(jwk_t)

      local jwk_pub, err = db.keys:get_pubkey(jwk_t)
      assert.is_nil(err)
      local jwk_pub_o = cjson.decode(jwk_pub)
      assert.is_not_nil(jwk_pub_o.e)
      assert.is_not_nil(jwk_pub_o.kid)
      assert.is_not_nil(jwk_pub_o.kty)
      assert.is_not_nil(jwk_pub_o.n)

      local jwk_priv, err_t = db.keys:get_privkey(jwk_t)
      local decoded_jwk = cjson.decode(jwk_priv)
      assert.is_nil(err_t)
      assert.is_not_nil(decoded_jwk.kid)
      assert.is_not_nil(decoded_jwk.kty)
      assert.is_not_nil(decoded_jwk.d)
      assert.is_not_nil(decoded_jwk.dp)
      assert.is_not_nil(decoded_jwk.dq)
      assert.is_not_nil(decoded_jwk.e)
      assert.is_not_nil(decoded_jwk.n)
      assert.is_not_nil(decoded_jwk.p)
      assert.is_not_nil(decoded_jwk.q)
      assert.is_not_nil(decoded_jwk.qi)
    end)

    it(":get_privkey errors if only got pubkey [pem]", function()
      local pem_t, err = db.keys:insert {
        name = "pem_key",
        set = init_key_set,
        kid = "999",
        pem = { public_key = pem_pub }
      }
      assert.is_nil(err)
      assert(pem_t)

      local pem_pub_t, g_err = db.keys:get_pubkey(pem_t)
      assert.is_nil(g_err)
      assert.matches("-----BEGIN PUBLIC KEY", pem_pub_t)

      local pem_priv, p_err = db.keys:get_privkey(pem_t)
      assert.is_nil(pem_priv)
      assert.matches("could not load a private key from public key material", p_err)
    end)

    it(":get_privkey errors if only got pubkey [jwk]", function()
      jwk.d = nil
      local jwk_t, _ = db.keys:insert {
        name = "jwk_key",
        set = init_key_set,
        kid = jwk.kid,
        jwk = cjson.encode(jwk)
      }
      assert(jwk_t)

      local jwk_pub_t, g_err = db.keys:get_pubkey(jwk_t)
      assert.is_nil(g_err)
      local jwk_pub_o = cjson.decode(jwk_pub_t)
      assert.is_not_nil(jwk_pub_o.e)
      assert.is_not_nil(jwk_pub_o.kid)
      assert.is_not_nil(jwk_pub_o.kty)
      assert.is_not_nil(jwk_pub_o.n)

      local jwk_priv, p_err = db.keys:get_privkey(jwk_t)
      assert.is_nil(jwk_priv)
      assert.matches("could not load a private key from public key material", p_err)
    end)
  end)
end
