defmodule Cloak.Ciphers.Deprecated.AES.GCMTest do
  use ExUnit.Case

  alias Cloak.Ciphers.Deprecated.AES.GCM, as: Cipher
  alias Cloak.Tags.Encoder

  @aad "AES256GCM"

  @key <<88, 29, 191, 211, 7, 144, 46, 28, 64, 105, 68, 81, 80, 107, 139, 89, 236, 220, 61, 197,
         56, 111, 194, 14, 178, 4, 45, 102, 177, 64, 128, 137>>

  # This hard-coded ciphertext is used for regression tests to ensure
  # that future versions of Cloak can still decrypt ciphertexts generated
  # by v1.0.3, prior to the :crypto changes in Erlang 24
  @v1_0_3_ciphertext <<65, 69, 83, 46, 71, 67, 77, 1, 2, 86, 49, 153, 218, 45, 125, 24, 92, 205,
                       245, 77, 205, 110, 192, 168, 255, 35, 58, 19, 192, 144, 2, 122, 76, 119,
                       28, 163, 230, 114, 34, 11, 37, 67, 247, 84, 156, 101, 163, 39, 88, 244, 71,
                       239, 145, 28, 40, 250, 82, 126, 81, 219, 172, 188, 84, 42, 171, 155, 172,
                       170, 222>>

  @opts [module_tag: "AES.GCM", tag: "V1", key: @key]

  describe ".encrypt/2" do
    test "raises error" do
      assert_raise RuntimeError, fn ->
        Cipher.encrypt("plaintext", @opts)
      end
    end
  end

  describe ".decrypt/2" do
    setup :create_ciphertext

    test "decrypts old ciphertext", %{ciphertext: ciphertext} do
      assert {:ok, plaintext} = Cipher.decrypt(ciphertext, @opts)
      assert plaintext == "plaintext"
    end

    test "returns error if :module_tag does not match", %{ciphertext: ciphertext} do
      assert :error == Cipher.decrypt(ciphertext, Keyword.merge(@opts, module_tag: "Other"))
    end

    test "returns error if :tag does not match", %{ciphertext: ciphertext} do
      assert :error == Cipher.decrypt(ciphertext, Keyword.merge(@opts, tag: "Other"))
    end

    test "returns error on invalid ciphertext" do
      assert :error == Cipher.decrypt(<<0, 1>>, @opts)
    end

    test "can decrypt a ciphertext generated by Cloak v1.0.3" do
      assert Cipher.decrypt(@v1_0_3_ciphertext, @opts) == {:ok, "Lorem ipsum dolor sit amet"}
    end
  end

  describe ".can_decrypt?/2" do
    setup :create_ciphertext

    test "returns true if opts match", %{ciphertext: ciphertext} do
      assert Cipher.can_decrypt?(ciphertext, @opts)
    end

    test "returns false if opts don't match", %{ciphertext: ciphertext} do
      refute Cipher.can_decrypt?(ciphertext, Keyword.merge(@opts, module_tag: "Other"))
      refute Cipher.can_decrypt?(ciphertext, Keyword.merge(@opts, tag: "Other"))
    end
  end

  defp create_ciphertext(_) do
    iv = :crypto.strong_rand_bytes(16)

    {ciphertext, ciphertag} =
      Cloak.Crypto.encrypt_one_time_aead(:aes_256_gcm, @opts[:key], iv, @aad, "plaintext")

    [
      ciphertext:
        @opts[:module_tag] <> Encoder.encode(@opts[:tag]) <> iv <> ciphertag <> ciphertext
    ]
  end
end
