require "test_helper"

class ActionPack::WebAuthn::PublicKeyCredential::CreationOptionsTest < ActiveSupport::TestCase
  setup do
    @relying_party = ActionPack::WebAuthn::RelyingParty.new(id: "example.com", name: "Example App")
    @options = ActionPack::WebAuthn::PublicKeyCredential::CreationOptions.new(
      id: "user-123",
      name: "user@example.com",
      display_name: "Test User",
      relying_party: @relying_party
    )
  end

  test "initializes with required parameters" do
    assert_equal "user-123", @options.id
    assert_equal "user@example.com", @options.name
    assert_equal "Test User", @options.display_name
    assert_equal @relying_party, @options.relying_party
  end

  test "generates base64url encoded challenge" do
    assert_match(/\A[A-Za-z0-9_-]+\z/, @options.challenge)
  end

  test "generates signed challenge containing nonce" do
    signed_message = Base64.urlsafe_decode64(@options.challenge)
    nonce = ActionPack::WebAuthn.challenge_verifier.verified(signed_message, purpose: "registration")

    assert_not_nil nonce
    assert_equal 32, Base64.strict_decode64(nonce).bytesize
  end

  test "as_json" do
    json = @options.as_json

    assert_equal @options.challenge, json["challenge"]

    assert_equal({ "id" => "example.com", "name" => "Example App" }, json["rp"])

    user = json["user"]
    assert_equal Base64.urlsafe_encode64("user-123", padding: false), user["id"]
    assert_equal "user@example.com", user["name"]
    assert_equal "Test User", user["displayName"]

    assert_equal [
      { "type" => "public-key", "alg" => -7 },
      { "type" => "public-key", "alg" => -8 },
      { "type" => "public-key", "alg" => -257 }
    ], json["pubKeyCredParams"]

    assert_equal "required", json["authenticatorSelection"]["residentKey"]
    assert_equal true, json["authenticatorSelection"]["requireResidentKey"]
    assert_equal "preferred", json["authenticatorSelection"]["userVerification"]
  end

  test "as_json includes residentKey in authenticatorSelection" do
    options = ActionPack::WebAuthn::PublicKeyCredential::CreationOptions.new(
      id: "user-123",
      name: "user@example.com",
      display_name: "Test User",
      resident_key: :required,
      relying_party: @relying_party
    )

    assert_equal "required", options.as_json["authenticatorSelection"]["residentKey"]
    assert_equal true, options.as_json["authenticatorSelection"]["requireResidentKey"]
  end

  test "as_json excludes excludeCredentials when empty" do
    assert_nil @options.as_json["excludeCredentials"]
  end

  test "as_json includes excludeCredentials" do
    credentials = [
      build_credential(id: "cred-1", transports: [ "usb", "nfc" ]),
      build_credential(id: "cred-2", transports: [ "internal" ])
    ]

    options = ActionPack::WebAuthn::PublicKeyCredential::CreationOptions.new(
      id: "user-123",
      name: "user@example.com",
      display_name: "Test User",
      exclude_credentials: credentials,
      relying_party: @relying_party
    )

    assert_equal [
      { "type" => "public-key", "id" => "cred-1", "transports" => [ "usb", "nfc" ] },
      { "type" => "public-key", "id" => "cred-2", "transports" => [ "internal" ] }
    ], options.as_json["excludeCredentials"]
  end

  test "as_json excludes attestation when none" do
    assert_nil @options.as_json["attestation"]
  end

  test "as_json includes attestation when not none" do
    options = ActionPack::WebAuthn::PublicKeyCredential::CreationOptions.new(
      id: "user-123",
      name: "user@example.com",
      display_name: "Test User",
      attestation: :direct,
      relying_party: @relying_party
    )

    assert_equal "direct", options.as_json["attestation"]
  end

  test "raises with invalid attestation preference" do
    assert_raises(ActionPack::WebAuthn::InvalidOptionsError) do
      ActionPack::WebAuthn::PublicKeyCredential::CreationOptions.new(
        id: "user-123",
        name: "user@example.com",
        display_name: "Test User",
        attestation: :invalid,
        relying_party: @relying_party
      )
    end
  end

  test "as_json excludeCredentials omits transports when empty" do
    options = ActionPack::WebAuthn::PublicKeyCredential::CreationOptions.new(
      id: "user-123",
      name: "user@example.com",
      display_name: "Test User",
      exclude_credentials: [ build_credential(id: "cred-1") ],
      relying_party: @relying_party
    )

    assert_equal [
      { "type" => "public-key", "id" => "cred-1" }
    ], options.as_json["excludeCredentials"]
  end

  private
    def build_credential(id:, transports: [])
      ActionPack::WebAuthn::PublicKeyCredential.new(
        id: id,
        public_key: OpenSSL::PKey::EC.generate("prime256v1"),
        sign_count: 0,
        transports: transports
      )
    end
end
