defmodule Berrypod.VaultTest do
  use Berrypod.DataCase, async: true

  alias Berrypod.Vault

  describe "encrypt/1 and decrypt/1" do
    test "round-trips a string successfully" do
      plaintext = "my_secret_api_key_12345"

      assert {:ok, ciphertext} = Vault.encrypt(plaintext)
      assert is_binary(ciphertext)
      assert ciphertext != plaintext

      assert {:ok, decrypted} = Vault.decrypt(ciphertext)
      assert decrypted == plaintext
    end

    test "produces different ciphertext for same plaintext (random IV)" do
      plaintext = "same_secret"

      {:ok, ciphertext1} = Vault.encrypt(plaintext)
      {:ok, ciphertext2} = Vault.encrypt(plaintext)

      assert ciphertext1 != ciphertext2

      # Both should decrypt to same value
      assert {:ok, ^plaintext} = Vault.decrypt(ciphertext1)
      assert {:ok, ^plaintext} = Vault.decrypt(ciphertext2)
    end

    test "handles empty string" do
      assert {:ok, ciphertext} = Vault.encrypt("")
      assert {:ok, ""} = Vault.decrypt(ciphertext)
    end

    test "handles unicode characters" do
      plaintext = "héllo wörld 你好 🎉"

      {:ok, ciphertext} = Vault.encrypt(plaintext)
      {:ok, decrypted} = Vault.decrypt(ciphertext)

      assert decrypted == plaintext
    end

    test "handles long strings" do
      plaintext = String.duplicate("a", 10_000)

      {:ok, ciphertext} = Vault.encrypt(plaintext)
      {:ok, decrypted} = Vault.decrypt(ciphertext)

      assert decrypted == plaintext
    end

    test "encrypt/1 returns {:ok, nil} for nil input" do
      assert {:ok, nil} = Vault.encrypt(nil)
    end

    test "decrypt/1 returns {:ok, nil} for nil input" do
      assert {:ok, nil} = Vault.decrypt(nil)
    end

    test "decrypt/1 returns {:ok, empty} for empty string" do
      assert {:ok, ""} = Vault.decrypt("")
    end

    test "decrypt/1 returns error for invalid ciphertext" do
      assert {:error, :invalid_ciphertext} = Vault.decrypt("not_valid")
      assert {:error, :invalid_ciphertext} = Vault.decrypt(<<1, 2, 3>>)
    end

    test "decrypt/1 returns error for tampered ciphertext" do
      {:ok, ciphertext} = Vault.encrypt("secret")

      # Tamper with the ciphertext (flip a bit in the middle)
      tampered = :binary.bin_to_list(ciphertext)
      middle = div(length(tampered), 2)
      tampered = List.update_at(tampered, middle, &Bitwise.bxor(&1, 0xFF))
      tampered = :binary.list_to_bin(tampered)

      assert {:error, :decryption_failed} = Vault.decrypt(tampered)
    end
  end

  describe "encrypt!/1 and decrypt!/1" do
    test "round-trips successfully" do
      plaintext = "test_secret"

      ciphertext = Vault.encrypt!(plaintext)
      decrypted = Vault.decrypt!(ciphertext)

      assert decrypted == plaintext
    end

    test "encrypt!/1 raises on invalid input type" do
      assert_raise FunctionClauseError, fn ->
        Vault.encrypt!(123)
      end
    end

    test "decrypt!/1 raises on invalid ciphertext" do
      assert_raise RuntimeError, ~r/Decryption failed/, fn ->
        Vault.decrypt!("invalid")
      end
    end
  end
end
