# typed: false
# coding: utf-8

require 'openssl'

describe PDF::Reader::AesV2SecurityHandler do
  let(:key) { "test_encryption_key" }
  let(:handler) { PDF::Reader::AesV2SecurityHandler.new(key) }
  let(:reference) { PDF::Reader::Reference.new(1, 0) }

  describe "#decrypt" do
    context "with ciphertext that is not a multiple of 16 bytes" do
      it "raises MalformedPDFError" do
        invalid_buf = "short"
        expect {
          handler.decrypt(invalid_buf, reference)
        }.to raise_error(PDF::Reader::MalformedPDFError, "Ciphertext not a multiple of 16")
      end
    end

    context "with exactly 16 bytes (IV only)" do
      it "returns empty string" do
        iv_only = "0123456789abcdef"
        result = handler.decrypt(iv_only, reference)
        expect(result).to eq("")
      end
    end

    context "with valid padded ciphertext" do
      it "decrypts successfully with PKCS#7 padding" do
        plaintext = "Hello, World!"
        buf = encrypt_test_data(plaintext, key, reference, padding: true)

        result = handler.decrypt(buf, reference)
        expect(result).to eq(plaintext)
      end
    end

    context "with ciphertext without padding and valid key" do
      it "should decrypt successfully with no padding" do
        plaintext = "1234567890123456" # exactly 16 bytes
        buf = encrypt_test_data(plaintext, key, reference, padding: false)

        result = handler.decrypt(buf, reference)
        expect(result).to eq(plaintext)
      end
    end

    context "with ciphertext without padding and invalid key" do
      it "returns incorrect data when key is wrong" do
        wrong_key = "wrong_key_here!"
        wrong_handler = PDF::Reader::AesV2SecurityHandler.new(wrong_key)

        plaintext = "1234567890123456"
        buf = encrypt_test_data(plaintext, key, reference, padding: false)

        # Try to decrypt with wrong key - should return garbage, not raise error
        result = wrong_handler.decrypt(buf, reference)
        expect(result).not_to eq(plaintext)
        expect(result).to be_a(String)
      end
    end

    context "with padded ciphertext and invalid key" do
      it "returns incorrect data when key is wrong" do
        wrong_key = "wrong_key_here!"
        wrong_handler = PDF::Reader::AesV2SecurityHandler.new(wrong_key)

        plaintext = "Hello, World!"
        buf = encrypt_test_data(plaintext, key, reference, padding: true)

        # Try to decrypt with wrong key - should return garbage, not raise error
        result = wrong_handler.decrypt(buf, reference)
        expect(result).not_to eq(plaintext)
        expect(result).to be_a(String)
      end
    end

    context "with malformed ciphertext" do
      it "returns a string with garbage content" do
        # Create invalid ciphertext (random bytes, must be multiple of 16)
        iv = "0123456789abcdef"
        corrupted_data = "corrupted_data16" # exactly 16 bytes
        buf = iv + corrupted_data

        result = handler.decrypt(buf, reference)
        expect(result).to be_a(String)
      end
    end
  end

  private

  # Helper method to create encrypted test data
  # @param plaintext [String] the text to encrypt
  # @param encryption_key [String] the encryption key to use
  # @param ref [PDF::Reader::Reference] the PDF reference for key generation
  # @param padding [Boolean] whether to use PKCS#7 padding
  # @return [String] IV + ciphertext ready for decrypt method
  def encrypt_test_data(plaintext, encryption_key, ref, padding: true)
    # Generate the same object key that the handler would generate
    obj_key = encryption_key.dup
    (0..2).each { |e| obj_key << (ref.id >> e*8 & 0xFF) }
    (0..1).each { |e| obj_key << (ref.gen >> e*8 & 0xFF) }
    obj_key << 'sAlT'
    length = obj_key.length < 16 ? obj_key.length : 16
    digest_key = Digest::MD5.digest(obj_key)[0, length]

    # Encrypt with or without padding
    cipher = OpenSSL::Cipher.new("AES-#{length << 3}-CBC")
    cipher.encrypt
    cipher.padding = 0 unless padding
    cipher.key = digest_key
    iv = cipher.random_iv
    ciphertext = cipher.update(plaintext) + cipher.final

    # Return IV + ciphertext for the handler
    iv + ciphertext
  end
end
